[fidlc] Restructure type compilation

This CL adds a flat::TypeConstructorNew node that mirrors the
more general form that exists in the new syntax (i.e. a general
layout with parameters and constraints), and then introduces
a top level flat::TypeConstructor that contains either a
TypeConstructorNew or a TypeConstructorOld. All code is updated
to handle either kind of type constructor.

In order to properly support type aliases in the more general form,
compilation is changed so that Types are responsible for applying
constraints.

Fixed: 75122
Bug: 75112
Bug: 74193
Test: fx ninja -C out/default host_x64/fidl-compiler &&
./out/default/host_x64-asan/exe.unstripped/fidl-compiler
Test: fx test --host fidlc_golden_tests

Change-Id: I9ecce21e17732bef85afe9f3df2bb19fed7f977b
Reviewed-on: https://fuchsia-review.googlesource.com/c/fuchsia/+/516460
Commit-Queue: Felix Zhu <fcz@google.com>
Reviewed-by: Pascal-Louis Perez <pascallouis@google.com>
Reviewed-by: Alex Zaslavsky <azaslavsky@google.com>
diff --git a/tools/fidl/fidlc/include/fidl/diagnostics.h b/tools/fidl/fidlc/include/fidl/diagnostics.h
index 4b4d2ab..849ecb6 100644
--- a/tools/fidl/fidlc/include/fidl/diagnostics.h
+++ b/tools/fidl/fidlc/include/fidl/diagnostics.h
@@ -144,18 +144,16 @@
 constexpr ErrorDef<std::string_view> ErrUnknownEnumMember("unknown enum member '{}'");
 constexpr ErrorDef<std::string_view> ErrUnknownBitsMember("unknown bits member '{}'");
 constexpr ErrorDef<flat::Name, std::string_view> ErrNewTypesNotAllowed(
-    "newtypes not allowed: type declaration {} defines a new type of the existing {} type, wh is "
-    "not yet supported");
-constexpr ErrorDef<flat::IdentifierConstant *> ErrExpectedValueButGotType(
-    "{} is a type, but a value was expected");
+    "newtypes not allowed: type declaration {} defines a new type of the existing {} type, which "
+    "is not yet supported");
+constexpr ErrorDef<> ErrAnonymousTypesNotAllowed(
+    "anonymous layouts are not yet supported: layouts must be specified in a `type MyLayout = ...` "
+    "layout introduction statement.");
+constexpr ErrorDef<flat::Name> ErrExpectedValueButGotType("{} is a type, but a value was expected");
 constexpr ErrorDef<flat::Name, flat::Name> ErrMismatchedNameTypeAssignment(
     "mismatched named type assignment: cannot define a constant or default value of type {} "
     "using a value of type {}");
-constexpr ErrorDef<flat::Name, size_t, size_t> ErrConstraintsOverflow(
-    "{} is of a type that may only carry {} constraints, but {} were found");
-constexpr ErrorDef<flat::Name, std::string_view> ErrConstraintOptionalMisspelled(
-    "the final constraint on {} was expected to be optional, not {}");
-constexpr ErrorDef<flat::IdentifierConstant *, const flat::TypeConstructorOld *, const flat::Type *>
+constexpr ErrorDef<flat::IdentifierConstant *, const flat::Type *, const flat::Type *>
     ErrCannotConvertConstantToType("{}, of type {}, cannot be converted to type {}");
 constexpr ErrorDef<flat::LiteralConstant *, uint64_t, const flat::Type *>
     ErrStringConstantExceedsSizeBound("{} (string:{}) exceeds the size bound of type {}");
@@ -292,6 +290,7 @@
 // Type Templates
 // ---------------------------------------------------------------------------
 constexpr ErrorDef<flat::Name> ErrUnknownType("unknown type {}");
+// old style
 constexpr ErrorDef<const flat::TypeTemplate *> ErrMustBeAProtocol("{} must be a protocol");
 constexpr ErrorDef<const flat::TypeTemplate *> ErrCannotParameterizeAlias(
     "{}: aliases cannot be parameterized");
@@ -303,8 +302,30 @@
 constexpr ErrorDef<const flat::TypeTemplate *> ErrMustHaveNonZeroSize("{} must have non-zero size");
 constexpr ErrorDef<const flat::TypeTemplate *> ErrCannotBeParameterized(
     "{} cannot be parametrized");
+// TODO(fxbug.dev/74683): This is a copy of ErrCannotBeParameterized that is thrown earlier in the
+// compilation process (if we see an anonymous layout that has arguments `foo { ... }<T>`, we know
+// that this is invalid without needing to compile `foo` because no user-defined layouts support
+// arguments). Once we fully implement anonymous layouts, we should probably have the layout go
+// through the same compilation process as any other type, thereby removing the need to have this
+// separate error.
+constexpr ErrorDef<flat::Name> ErrLayoutCannotBeParameterized("{} cannot be parametrized");
+// TODO(fxbug.dev/74683): Support for anonymous layouts should include support for this as well.
+constexpr ErrorDef<> ErrCannotConstrainInLayoutDecl(
+    "cannot add constraints in this position; constraints must be added at the use-site");
 constexpr ErrorDef<const flat::TypeTemplate *> ErrCannotHaveSize("{} cannot have size");
 constexpr ErrorDef<const flat::TypeTemplate *> ErrCannotBeNullable("{} cannot be nullable");
+// new style
+constexpr ErrorDef<const flat::TypeTemplate *, size_t, size_t> ErrWrongNumberOfLayoutParameters(
+    "{} expected {} layout parameter(s), but got {}");
+constexpr ErrorDef<const flat::TypeTemplate *, size_t, size_t> ErrTooManyConstraints(
+    "{} expected at most {} constraints, but got {}");
+constexpr ErrorDef<> ErrExpectedType("expected type but got a literal or constant");
+constexpr ErrorDef<const flat::TypeTemplate *> ErrUnexpectedConstraint(
+    "{} failed to resolve constraint");
+// TODO(fxbug.dev/74193): Remove this error and allow re-constraining.
+constexpr ErrorDef<const flat::TypeTemplate *> ErrCannotConstrainTwice(
+    "{} cannot add additional constraint");
+// other
 constexpr ErrorDef<> ErrHandleSubtypeNotResource("handle subtype is not a defined resource");
 constexpr ErrorDef<flat::Name> ErrResourceMustBeUint32Derived("resource {} must be uint32");
 constexpr ErrorDef<flat::Name> ErrResourceMissingSubtypeProperty(
diff --git a/tools/fidl/fidlc/include/fidl/flat/types.h b/tools/fidl/fidlc/include/fidl/flat/types.h
index 2d6ff7d..bc751782 100644
--- a/tools/fidl/fidlc/include/fidl/flat/types.h
+++ b/tools/fidl/fidlc/include/fidl/flat/types.h
@@ -12,8 +12,13 @@
 namespace fidl {
 namespace flat {
 
+struct CreateInvocation;
 struct TypeDecl;
+class LibraryMediator;
+struct LayoutInvocation;
+struct TypeConstraints;
 struct Resource;
+class TypeTemplate;
 
 struct Type : public Object {
   virtual ~Type() {}
@@ -76,6 +81,29 @@
     assert(kind == other.kind);
     return Comparison().Compare(nullability, other.nullability);
   }
+
+  // The old syntax's CreateInvocation contains both what would considered to be
+  // layout arguments and constraints in the new syntax. These are applied to
+  // the current type to produce a new type - this is the old syntax equivalent
+  // of ApplyConstraints
+  virtual bool ApplySomeLayoutParametersAndConstraints(const flat::LibraryMediator& lib,
+                                                       const CreateInvocation& create_invocation,
+                                                       const flat::TypeTemplate* layout,
+                                                       std::unique_ptr<Type>* out_type,
+                                                       LayoutInvocation* out_params) const = 0;
+
+  // Apply the provided constraints to this type, returning the newly constrained
+  // Type and recording the invocation inside resolved_args.
+  // For types in the new syntax, we receive the unresolved TypeConstraints.
+  // TODO(fxbug.dev/74193): We currently require a pointer to the calling TypeTemplate
+  // for error reporting purposes, since all of the constraint related errors are
+  // still tied to TypeTemplates. As we fully separate out the constraints and
+  // layout parameter (TypeTemplate::Create) code, we'll be able to remove this
+  // extraneous parameter.
+  virtual bool ApplyConstraints(const flat::LibraryMediator& lib,
+                                const TypeConstraints& constraints,
+                                const flat::TypeTemplate* layout, std::unique_ptr<Type>* out_type,
+                                LayoutInvocation* out_params) const = 0;
 };
 
 struct ArrayType final : public Type {
@@ -95,9 +123,36 @@
         .Compare(element_count->value, o.element_count->value)
         .Compare(*element_type, *o.element_type);
   }
+
+  bool ApplySomeLayoutParametersAndConstraints(const flat::LibraryMediator& lib,
+                                               const CreateInvocation& create_invocation,
+                                               const flat::TypeTemplate* layout,
+                                               std::unique_ptr<Type>* out_type,
+                                               LayoutInvocation* out_params) const override;
+
+  bool ApplyConstraints(const flat::LibraryMediator& lib, const TypeConstraints& constraints,
+                        const flat::TypeTemplate* layout, std::unique_ptr<Type>* out_type,
+                        LayoutInvocation* out_params) const override;
 };
 
-struct VectorType final : public Type {
+struct VectorBaseType {
+  // "vector based" types share common code for determining the size and nullability.
+  // This method provides the resolved size and nullability, so that specific implementations
+  // only need to worry about setting the element type on out_args.
+  // We can't abstract away only the element type resolution process, because not
+  // all vector based type templates return a VectorType (the exception being StringTypeTemplate).
+  static bool ResolveSizeAndNullability(const LibraryMediator& lib,
+                                        const TypeConstraints& constraints,
+                                        const TypeTemplate* layout, LayoutInvocation* out_params);
+
+  const static Size kMaxSize;
+};
+
+struct VectorType final : public Type, public VectorBaseType {
+  VectorType(const Name& name, const Type* element_type)
+      : Type(name, Kind::kVector, types::Nullability::kNonnullable),
+        element_type(element_type),
+        element_count(&kMaxSize) {}
   VectorType(const Name& name, const Type* element_type, const Size* element_count,
              types::Nullability nullability)
       : Type(name, Kind::kVector, nullability),
@@ -115,9 +170,21 @@
         .Compare(element_count->value, o.element_count->value)
         .Compare(*element_type, *o.element_type);
   }
+
+  bool ApplySomeLayoutParametersAndConstraints(const flat::LibraryMediator& lib,
+                                               const CreateInvocation& create_invocation,
+                                               const flat::TypeTemplate* layout,
+                                               std::unique_ptr<Type>* out_type,
+                                               LayoutInvocation* out_params) const override;
+
+  bool ApplyConstraints(const flat::LibraryMediator& lib, const TypeConstraints& constraints,
+                        const flat::TypeTemplate* layout, std::unique_ptr<Type>* out_type,
+                        LayoutInvocation* out_params) const override;
 };
 
-struct StringType final : public Type {
+struct StringType final : public Type, public VectorBaseType {
+  explicit StringType(const Name& name)
+      : Type(name, Kind::kString, types::Nullability::kNonnullable), max_size(&kMaxSize) {}
   StringType(const Name& name, const Size* max_size, types::Nullability nullability)
       : Type(name, Kind::kString, nullability), max_size(max_size) {}
 
@@ -129,9 +196,31 @@
     const auto& o = static_cast<const StringType&>(other);
     return Type::Compare(o).Compare(max_size->value, o.max_size->value);
   }
+
+  bool ApplySomeLayoutParametersAndConstraints(const flat::LibraryMediator& lib,
+                                               const CreateInvocation& create_invocation,
+                                               const flat::TypeTemplate* layout,
+                                               std::unique_ptr<Type>* out_type,
+                                               LayoutInvocation* out_params) const override;
+
+  bool ApplyConstraints(const flat::LibraryMediator& lib, const TypeConstraints& constraints,
+                        const flat::TypeTemplate* layout, std::unique_ptr<Type>* out_type,
+                        LayoutInvocation* out_params) const override;
 };
 
 struct HandleType final : public Type {
+  HandleType(const Name& name, Resource* resource_decl)
+      : Type(name, Kind::kHandle, types::Nullability::kNonnullable),
+        resource_decl(resource_decl),
+        // TODO(fxbug.dev/64629): When we are ready to create handle types, we
+        // should have an object type (and/or subtype) determined and not require
+        // these hardcoded defaults.
+        // We need to allow setting a default obj_type in resource_definition
+        // declarations rather than hard-coding.
+        obj_type(static_cast<uint32_t>(types::HandleSubtype::kHandle)),
+        subtype(types::HandleSubtype::kHandle),
+        rights(&kSameRights) {}
+
   HandleType(const Name& name, Resource* resource_decl, uint32_t obj_type,
              types::HandleSubtype subtype, const HandleRights* rights,
              types::Nullability nullability)
@@ -159,6 +248,18 @@
         .Compare(subtype, other_handle_type.subtype)
         .Compare(*rights_val, *other_rights_val);
   }
+
+  bool ApplySomeLayoutParametersAndConstraints(const flat::LibraryMediator& lib,
+                                               const CreateInvocation& create_invocation,
+                                               const flat::TypeTemplate* layout,
+                                               std::unique_ptr<Type>* out_type,
+                                               LayoutInvocation* out_params) const override;
+
+  bool ApplyConstraints(const flat::LibraryMediator& lib, const TypeConstraints& constraints,
+                        const flat::TypeTemplate* layout, std::unique_ptr<Type>* out_type,
+                        LayoutInvocation* out_params) const override;
+
+  const static HandleRights kSameRights;
 };
 
 struct PrimitiveType final : public Type {
@@ -174,12 +275,24 @@
     return Type::Compare(o).Compare(subtype, o.subtype);
   }
 
+  bool ApplySomeLayoutParametersAndConstraints(const flat::LibraryMediator& lib,
+                                               const CreateInvocation& create_invocation,
+                                               const flat::TypeTemplate* layout,
+                                               std::unique_ptr<Type>* out_type,
+                                               LayoutInvocation* out_params) const override;
+
+  bool ApplyConstraints(const flat::LibraryMediator& lib, const TypeConstraints& constraints,
+                        const flat::TypeTemplate* layout, std::unique_ptr<Type>* out_type,
+                        LayoutInvocation* out_params) const override;
+
  private:
   static uint32_t SubtypeSize(types::PrimitiveSubtype subtype);
 };
 
 struct IdentifierType final : public Type {
-  IdentifierType(const Name& name, types::Nullability nullability, const TypeDecl* type_decl)
+  IdentifierType(const Name& name, const TypeDecl* type_decl)
+      : IdentifierType(name, type_decl, types::Nullability::kNonnullable) {}
+  IdentifierType(const Name& name, const TypeDecl* type_decl, types::Nullability nullability)
       : Type(name, Kind::kIdentifier, nullability), type_decl(type_decl) {}
 
   const TypeDecl* type_decl;
@@ -190,10 +303,22 @@
     const auto& o = static_cast<const IdentifierType&>(other);
     return Type::Compare(o).Compare(name, o.name);
   }
+
+  bool ApplySomeLayoutParametersAndConstraints(const flat::LibraryMediator& lib,
+                                               const CreateInvocation& create_invocation,
+                                               const flat::TypeTemplate* layout,
+                                               std::unique_ptr<Type>* out_type,
+                                               LayoutInvocation* out_params) const override;
+
+  bool ApplyConstraints(const flat::LibraryMediator& lib, const TypeConstraints& constraints,
+                        const flat::TypeTemplate* layout, std::unique_ptr<Type>* out_type,
+                        LayoutInvocation* out_params) const override;
 };
 
 // TODO(fxbug.dev/43803) Add required and optional rights.
 struct RequestHandleType final : public Type {
+  RequestHandleType(const Name& name, const IdentifierType* protocol_type)
+      : RequestHandleType(name, protocol_type, types::Nullability::kNonnullable) {}
   RequestHandleType(const Name& name, const IdentifierType* protocol_type,
                     types::Nullability nullability)
       : Type(name, Kind::kRequestHandle, nullability), protocol_type(protocol_type) {}
@@ -206,6 +331,16 @@
     const auto& o = static_cast<const RequestHandleType&>(other);
     return Type::Compare(o).Compare(*protocol_type, *o.protocol_type);
   }
+
+  bool ApplySomeLayoutParametersAndConstraints(const flat::LibraryMediator& lib,
+                                               const CreateInvocation& create_invocation,
+                                               const flat::TypeTemplate* layout,
+                                               std::unique_ptr<Type>* out_type,
+                                               LayoutInvocation* out_params) const override;
+
+  bool ApplyConstraints(const flat::LibraryMediator& lib, const TypeConstraints& constraints,
+                        const flat::TypeTemplate* layout, std::unique_ptr<Type>* out_type,
+                        LayoutInvocation* out_params) const override;
 };
 
 }  // namespace flat
diff --git a/tools/fidl/fidlc/include/fidl/flat_ast.h b/tools/fidl/fidlc/include/fidl/flat_ast.h
index ae83602..8edb3a1 100644
--- a/tools/fidl/fidlc/include/fidl/flat_ast.h
+++ b/tools/fidl/fidlc/include/fidl/flat_ast.h
@@ -102,37 +102,105 @@
   bool recursive = false;
 };
 
+// TODO(fxbug.dev/70247): Remove the two type constructor versions and remove
+// the New suffix.
+struct TypeConstructorOld;
+struct TypeConstructorNew;
+using TypeConstructor =
+    std::variant<std::unique_ptr<TypeConstructorNew>, std::unique_ptr<TypeConstructorOld>>;
+
+// Functions that recursively traverse a type constructor can't operate on
+// const std::unique_ptr<T>& because the inner type constructors accessed through
+// LayoutInvocation::element_type_raw are pointers. To get around this, the top
+// level call must extract the pointers out, so that the function can be defined
+// for and called recursively over pointers.
+using TypeConstructorPtr = std::variant<const TypeConstructorNew*, const TypeConstructorOld*>;
+
+TypeConstructorPtr GetTypeCtorAsPtr(const TypeConstructor& type_ctor);
+
+template <typename T>
+const Type* GetType(const T& type_ctor) {
+  return std::visit([](const auto& ptr) { return ptr->type; }, type_ctor);
+}
+
+template <typename T>
+bool IsTypeConstructorDefined(const T& type_ctor) {
+  return std::visit([](const auto& ptr) { return ptr != nullptr; }, type_ctor);
+}
+
+template <typename T>
+Name GetName(const T& type_ctor) {
+  return std::visit([](const auto& ptr) { return ptr->name; }, type_ctor);
+}
+
+template <typename T>
+const LayoutInvocation& GetLayoutInvocation(const T& type_ctor) {
+  return std::visit([](const auto& ptr) -> const LayoutInvocation& { return ptr->resolved_params; },
+                    type_ctor);
+}
+
 struct TypeAlias;
 
-struct TypeConstructorOld final {
-  struct FromTypeAlias {
-    FromTypeAlias(const TypeAlias* decl, const Type* maybe_arg_type, const Size* maybe_size,
-                  std::optional<types::HandleSubtype> maybe_handle_subtype,
-                  types::Nullability nullability) noexcept
-        : decl(decl),
-          maybe_arg_type(maybe_arg_type),
-          maybe_size(maybe_size),
-          maybe_handle_subtype(maybe_handle_subtype),
-          nullability(nullability) {}
-    const TypeAlias* decl;
-    const Type* maybe_arg_type;
-    const Size* maybe_size;
-    std::optional<types::HandleSubtype> maybe_handle_subtype;
-    // TODO(pascallouis): Make this const.
-    types::Nullability nullability;
-  };
+// This is a struct used to group together all data produced during compilation
+// that might be used by consumers that are downstream from type compilation
+// (e.g. typeshape code, declaration sorting, JSON generator), that can't be
+// obtained by looking at a type constructor's Type.
+// Unlike TypeConstructor::Type which will always refer to the fully resolved/
+// concrete (and eventually, canonicalized) type that the type constructor
+// resolves to, this struct stores data about the actual parameters on this
+// type constructor used to produce the type.
+// This struct overlaps with CreateInvocation because they both store resolved
+// parameters/constraints. This overlap will no longer exist after the old syntax
+// and CreateInvocation are cleaned up - LayoutInvocation is in some sense the
+// successor to CreateInvocation.
+// These fields should be set in the same place where the parameters actually get
+// resolved - for the new syntax, this is in Create (for layout parameters) and
+// in ApplyConstraints (for type constraints) but in the old syntax they are all
+// set in ResolveOldSyntaxArgs.
+struct LayoutInvocation {
+  // set if this type constructor refers to a type alias
+  const TypeAlias* from_type_alias = nullptr;
 
+  // Parameter data below: if a foo_resolved form is set, then its corresponding
+  // foo_raw form must be defined as well (and vice versa).
+
+  // resolved form of this type constructor's arguments
+  const Type* element_type_resolved = nullptr;
+  const Size* size_resolved = nullptr;
+  // This has no users, probably because it's missing in the JSON IR (it is not
+  // yet generated for experimental_maybe_from_type_alias)
+  std::optional<uint32_t> subtype_resolved = std::nullopt;
+  // This has no users, probably because it's missing in the JSON IR (it is not
+  // yet generated for experimental_maybe_from_type_alias).
+  const HandleRights* rights_resolved = nullptr;
+
+  // raw form of this type constructor's arguments
+  TypeConstructorPtr element_type_raw = {};
+  const Constant* size_raw = nullptr;
+  // This has no users, probably because it's missing in the JSON IR (it is not
+  // yet generated for partial_type_ctors). Notably, this is probably because this
+  // is parsed as a Name in the old syntax but the consistent thing to do would
+  // be to emit it as a Constant, which made it awkward to implement (and thus
+  // was never done). This field is never set in the old syntax.
+  const Constant* subtype_raw = nullptr;
+  const Constant* rights_raw = nullptr;
+
+  // Nullability is represented differently because there's only one degree of
+  // freedom: if it was specified, this value is equal to kNullable
+  types::Nullability nullability = types::Nullability::kNonnullable;
+};
+
+struct TypeConstructorOld final {
   TypeConstructorOld(Name name, std::unique_ptr<TypeConstructorOld> maybe_arg_type_ctor,
                      std::optional<Name> handle_subtype_identifier,
                      std::unique_ptr<Constant> handle_rights, std::unique_ptr<Constant> maybe_size,
-                     types::Nullability nullability, fidl::utils::Syntax syntax)
+                     types::Nullability nullability)
       : name(std::move(name)),
         maybe_arg_type_ctor(std::move(maybe_arg_type_ctor)),
         handle_subtype_identifier(std::move(handle_subtype_identifier)),
         handle_rights(std::move(handle_rights)),
         maybe_size(std::move(maybe_size)),
-        nullability(nullability),
-        syntax(syntax) {}
+        nullability(nullability) {}
 
   // Returns a type constructor for the size type (used for bounds).
   static std::unique_ptr<TypeConstructorOld> CreateSizeType();
@@ -144,11 +212,131 @@
   std::unique_ptr<Constant> handle_rights;
   std::unique_ptr<Constant> maybe_size;
   types::Nullability nullability;
-  const fidl::utils::Syntax syntax;
 
   // Set during compilation.
   const Type* type = nullptr;
-  std::optional<FromTypeAlias> from_type_alias;
+  LayoutInvocation resolved_params;
+};
+
+struct LayoutParameterList;
+struct TypeConstraints;
+
+struct TypeConstructorNew final {
+  TypeConstructorNew(Name name, std::unique_ptr<LayoutParameterList> parameters,
+                     std::unique_ptr<TypeConstraints> constraints)
+      : name(std::move(name)),
+        parameters(std::move(parameters)),
+        constraints(std::move(constraints)) {}
+
+  // Returns a type constructor for the size type (used for bounds).
+  static std::unique_ptr<TypeConstructorNew> CreateSizeType();
+
+  // Set during construction.
+  // TODO(fxbug.dev/74683): Until we settle on an approach, avoid premature
+  // optimization and do the simple thing of assuming all type constructors at
+  // this point have a named reference to an existing Decl.
+  const Name name;
+  std::unique_ptr<LayoutParameterList> parameters;
+  std::unique_ptr<TypeConstraints> constraints;
+
+  // Set during compilation.
+  const Type* type = nullptr;
+  LayoutInvocation resolved_params;
+};
+
+struct LayoutParameter {
+ public:
+  virtual ~LayoutParameter() = default;
+  enum Kind {
+    kIdentifier,
+    kLiteral,
+    kType,
+  };
+
+  explicit LayoutParameter(Kind kind, SourceSpan span) : kind(kind), span(span) {}
+
+  // TODO(fxbug.dev/75112): Providing these virtual methods rather than handling
+  // each case individually in the caller makes it harder to provide more precise
+  // error messages. For example, using this pattern we'd only know that a parameter
+  // failed to be interpreted as a type and not the specifics about why it failed
+  // (was this actually a string literal? did it look like a type but fail to
+  // resolve? did it look like a type but actually point to a constant?).
+  // Addressing the bug might involve refactoring this part of the code to move
+  // more logic into the caller. This might be acceptable when the caller is
+  // type compilation (it probably needs to know these details anyways), but
+  // less so when it's a consumer of compiled results that needs to reconstruct
+  // details about the type constructor (e.g. during declaration sorting or
+  // JSON generation).
+
+  // TODO(fxbug.dev/75805): The return types should be optional references
+
+  // Returns the interpretation of this layout parameter as a type if possible
+  // or nullptr otherwise. There are no guarantees that the returned type has
+  // been compiled or will actually successfully compile.
+  virtual TypeConstructorNew* AsTypeCtor() const = 0;
+
+  // Returns the interpretation of this layout parameter as a constant if possible
+  // or nullptr otherwise. There are no guarantees that the returned constant has
+  // been compiled or will actually successfully compile.
+  virtual Constant* AsConstant() const = 0;
+
+  const Kind kind;
+  SourceSpan span;
+};
+
+struct LiteralLayoutParameter final : public LayoutParameter {
+  explicit LiteralLayoutParameter(std::unique_ptr<LiteralConstant> literal, SourceSpan span)
+      : LayoutParameter(Kind::kLiteral, span), literal(std::move(literal)) {}
+
+  TypeConstructorNew* AsTypeCtor() const override;
+  Constant* AsConstant() const override;
+  std::unique_ptr<LiteralConstant> literal;
+};
+
+struct TypeLayoutParameter final : public LayoutParameter {
+  explicit TypeLayoutParameter(std::unique_ptr<TypeConstructorNew> type_ctor, SourceSpan span)
+      : LayoutParameter(Kind::kType, span), type_ctor(std::move(type_ctor)) {}
+
+  TypeConstructorNew* AsTypeCtor() const override;
+  Constant* AsConstant() const override;
+  std::unique_ptr<TypeConstructorNew> type_ctor;
+};
+
+struct IdentifierLayoutParameter final : public LayoutParameter {
+  explicit IdentifierLayoutParameter(Name name, SourceSpan span)
+      : LayoutParameter(Kind::kIdentifier, span), name(std::move(name)) {}
+
+  TypeConstructorNew* AsTypeCtor() const override;
+  Constant* AsConstant() const override;
+
+  // Stores an interpretation of this layout as a TypeConstructor, if asked
+  // at some point (i.e. on demand by calling AsTypeCtor). We store this to
+  // store a reference to the compiled Type and LayoutInvocation
+  mutable std::unique_ptr<TypeConstructorNew> as_type_ctor;
+
+  // Stores an interpretation of this layout as a Constant, if asked at some
+  // point (i.e. on demand by calling AsConstant). We store this to store a
+  // reference to the compiled ConstantValue
+  mutable std::unique_ptr<Constant> as_constant;
+  const Name name;
+};
+
+struct LayoutParameterList {
+  explicit LayoutParameterList(std::vector<std::unique_ptr<LayoutParameter>> items,
+                               std::optional<SourceSpan> span)
+      : items(std::move(items)), span(span) {}
+
+  std::vector<std::unique_ptr<LayoutParameter>> items;
+  const std::optional<SourceSpan> span;
+};
+
+struct TypeConstraints {
+  explicit TypeConstraints(std::vector<std::unique_ptr<Constant>> items,
+                           std::optional<SourceSpan> span)
+      : items(std::move(items)), span(span) {}
+
+  std::vector<std::unique_ptr<Constant>> items;
+  const std::optional<SourceSpan> span;
 };
 
 struct Using final {
@@ -162,12 +350,12 @@
 // Constant. For the _value_, see ConstantValue.) A Const consists of a
 // left-hand-side Name (found in Decl) and a right-hand-side Constant.
 struct Const final : public Decl {
-  Const(std::unique_ptr<raw::AttributeList> attributes, Name name,
-        std::unique_ptr<TypeConstructorOld> type_ctor, std::unique_ptr<Constant> value)
+  Const(std::unique_ptr<raw::AttributeList> attributes, Name name, TypeConstructor type_ctor,
+        std::unique_ptr<Constant> value)
       : Decl(Kind::kConst, std::move(attributes), std::move(name)),
         type_ctor(std::move(type_ctor)),
         value(std::move(value)) {}
-  std::unique_ptr<TypeConstructorOld> type_ctor;
+  TypeConstructor type_ctor;
   std::unique_ptr<Constant> value;
 };
 
@@ -181,16 +369,15 @@
     std::unique_ptr<raw::AttributeList> attributes;
   };
 
-  Enum(std::unique_ptr<raw::AttributeList> attributes, Name name,
-       std::unique_ptr<TypeConstructorOld> subtype_ctor, std::vector<Member> members,
-       types::Strictness strictness)
+  Enum(std::unique_ptr<raw::AttributeList> attributes, Name name, TypeConstructor subtype_ctor,
+       std::vector<Member> members, types::Strictness strictness)
       : TypeDecl(Kind::kEnum, std::move(attributes), std::move(name)),
         subtype_ctor(std::move(subtype_ctor)),
         members(std::move(members)),
         strictness(strictness) {}
 
   // Set during construction.
-  std::unique_ptr<TypeConstructorOld> subtype_ctor;
+  TypeConstructor subtype_ctor;
   std::vector<Member> members;
   const types::Strictness strictness;
 
@@ -214,16 +401,15 @@
     std::unique_ptr<raw::AttributeList> attributes;
   };
 
-  Bits(std::unique_ptr<raw::AttributeList> attributes, Name name,
-       std::unique_ptr<TypeConstructorOld> subtype_ctor, std::vector<Member> members,
-       types::Strictness strictness)
+  Bits(std::unique_ptr<raw::AttributeList> attributes, Name name, TypeConstructor subtype_ctor,
+       std::vector<Member> members, types::Strictness strictness)
       : TypeDecl(Kind::kBits, std::move(attributes), std::move(name)),
         subtype_ctor(std::move(subtype_ctor)),
         members(std::move(members)),
         strictness(strictness) {}
 
   // Set during construction.
-  std::unique_ptr<TypeConstructorOld> subtype_ctor;
+  TypeConstructor subtype_ctor;
   std::vector<Member> members;
   const types::Strictness strictness;
 
@@ -235,13 +421,13 @@
 
 struct Service final : public TypeDecl {
   struct Member {
-    Member(std::unique_ptr<TypeConstructorOld> type_ctor, SourceSpan name,
+    Member(TypeConstructor type_ctor, SourceSpan name,
            std::unique_ptr<raw::AttributeList> attributes)
         : type_ctor(std::move(type_ctor)),
           name(std::move(name)),
           attributes(std::move(attributes)) {}
 
-    std::unique_ptr<TypeConstructorOld> type_ctor;
+    TypeConstructor type_ctor;
     SourceSpan name;
     std::unique_ptr<raw::AttributeList> attributes;
   };
@@ -262,14 +448,14 @@
 // backward-compatibility, Struct::Member is now an alias for this top-level StructMember.
 // TODO(fxbug.dev/37535): Move this to a nested class inside Struct.
 struct StructMember : public Object {
-  StructMember(std::unique_ptr<TypeConstructorOld> type_ctor, SourceSpan name,
+  StructMember(TypeConstructor type_ctor, SourceSpan name,
                std::unique_ptr<Constant> maybe_default_value,
                std::unique_ptr<raw::AttributeList> attributes)
       : type_ctor(std::move(type_ctor)),
         name(std::move(name)),
         maybe_default_value(std::move(maybe_default_value)),
         attributes(std::move(attributes)) {}
-  std::unique_ptr<TypeConstructorOld> type_ctor;
+  TypeConstructor type_ctor;
   SourceSpan name;
   std::unique_ptr<Constant> maybe_default_value;
   std::unique_ptr<raw::AttributeList> attributes;
@@ -314,14 +500,14 @@
 // See the comment on the StructMember class for why this is a top-level class.
 // TODO(fxbug.dev/37535): Move this to a nested class inside Table::Member.
 struct TableMemberUsed : public Object {
-  TableMemberUsed(std::unique_ptr<TypeConstructorOld> type_ctor, SourceSpan name,
+  TableMemberUsed(TypeConstructor type_ctor, SourceSpan name,
                   std::unique_ptr<Constant> maybe_default_value,
                   std::unique_ptr<raw::AttributeList> attributes)
       : type_ctor(std::move(type_ctor)),
         name(std::move(name)),
         maybe_default_value(std::move(maybe_default_value)),
         attributes(std::move(attributes)) {}
-  std::unique_ptr<TypeConstructorOld> type_ctor;
+  TypeConstructor type_ctor;
   SourceSpan name;
   std::unique_ptr<Constant> maybe_default_value;
   std::unique_ptr<raw::AttributeList> attributes;
@@ -336,14 +522,14 @@
 struct TableMember : public Object {
   using Used = TableMemberUsed;
 
-  TableMember(std::unique_ptr<raw::Ordinal64> ordinal, std::unique_ptr<TypeConstructorOld> type,
-              SourceSpan name, std::unique_ptr<Constant> maybe_default_value,
+  TableMember(std::unique_ptr<raw::Ordinal64> ordinal, TypeConstructor type, SourceSpan name,
+              std::unique_ptr<Constant> maybe_default_value,
               std::unique_ptr<raw::AttributeList> attributes)
       : ordinal(std::move(ordinal)),
         maybe_used(std::make_unique<Used>(std::move(type), name, std::move(maybe_default_value),
                                           std::move(attributes))) {}
-  TableMember(std::unique_ptr<raw::Ordinal64> ordinal, std::unique_ptr<TypeConstructorOld> type,
-              SourceSpan name, std::unique_ptr<raw::AttributeList> attributes)
+  TableMember(std::unique_ptr<raw::Ordinal64> ordinal, TypeConstructor type, SourceSpan name,
+              std::unique_ptr<raw::AttributeList> attributes)
       : ordinal(std::move(ordinal)),
         maybe_used(std::make_unique<Used>(std::move(type), name, nullptr, std::move(attributes))) {}
   TableMember(std::unique_ptr<raw::Ordinal64> ordinal, SourceSpan span)
@@ -381,10 +567,10 @@
 // See the comment on the StructMember class for why this is a top-level class.
 // TODO(fxbug.dev/37535): Move this to a nested class inside Union.
 struct UnionMemberUsed : public Object {
-  UnionMemberUsed(std::unique_ptr<TypeConstructorOld> type_ctor, SourceSpan name,
+  UnionMemberUsed(TypeConstructor type_ctor, SourceSpan name,
                   std::unique_ptr<raw::AttributeList> attributes)
       : type_ctor(std::move(type_ctor)), name(name), attributes(std::move(attributes)) {}
-  std::unique_ptr<TypeConstructorOld> type_ctor;
+  TypeConstructor type_ctor;
   SourceSpan name;
   std::unique_ptr<raw::AttributeList> attributes;
 
@@ -400,8 +586,7 @@
 struct UnionMember : public Object {
   using Used = UnionMemberUsed;
 
-  UnionMember(std::unique_ptr<raw::Ordinal64> ordinal,
-              std::unique_ptr<TypeConstructorOld> type_ctor, SourceSpan name,
+  UnionMember(std::unique_ptr<raw::Ordinal64> ordinal, TypeConstructor type_ctor, SourceSpan name,
               std::unique_ptr<raw::AttributeList> attributes)
       : ordinal(std::move(ordinal)),
         maybe_used(std::make_unique<Used>(std::move(type_ctor), name, std::move(attributes))) {}
@@ -503,32 +688,32 @@
 
 struct Resource final : public Decl {
   struct Property {
-    Property(std::unique_ptr<TypeConstructorOld> type_ctor, SourceSpan name,
+    Property(TypeConstructor type_ctor, SourceSpan name,
              std::unique_ptr<raw::AttributeList> attributes)
         : type_ctor(std::move(type_ctor)),
           name(std::move(name)),
           attributes(std::move(attributes)) {}
-    std::unique_ptr<TypeConstructorOld> type_ctor;
+    TypeConstructor type_ctor;
     SourceSpan name;
     std::unique_ptr<raw::AttributeList> attributes;
   };
 
-  Resource(std::unique_ptr<raw::AttributeList> attributes, Name name,
-           std::unique_ptr<TypeConstructorOld> subtype_ctor, std::vector<Property> properties)
+  Resource(std::unique_ptr<raw::AttributeList> attributes, Name name, TypeConstructor subtype_ctor,
+           std::vector<Property> properties)
       : Decl(Kind::kResource, std::move(attributes), std::move(name)),
         subtype_ctor(std::move(subtype_ctor)),
         properties(std::move(properties)) {}
 
   // Set during construction.
-  std::unique_ptr<TypeConstructorOld> subtype_ctor;
+  TypeConstructor subtype_ctor;
   std::vector<Property> properties;
 
-  const Property* LookupProperty(std::string_view name);
+  Property* LookupProperty(std::string_view name);
 };
 
 struct TypeAlias final : public Decl {
   TypeAlias(std::unique_ptr<raw::AttributeList> attributes, Name name,
-            std::unique_ptr<TypeConstructorOld> partial_type_ctor)
+            TypeConstructor partial_type_ctor)
       : Decl(Kind::kTypeAlias, std::move(attributes), std::move(name)),
         partial_type_ctor(std::move(partial_type_ctor)) {}
 
@@ -542,7 +727,7 @@
   // at any point in a "type alias chain" can specify a constraint, but any
   // constraint can only specified once. This behavior will change in
   // fxbug.dev/74193.
-  const std::unique_ptr<TypeConstructorOld> partial_type_ctor;
+  TypeConstructor partial_type_ctor;
 };
 
 // Wrapper class around a Library to provide specific methods to TypeTemplates.
@@ -553,8 +738,45 @@
  public:
   explicit LibraryMediator(Library* library) : library_(library) {}
 
-  // These methods forward their implementation to the library_
+  // Top level methods for resolving layout parameters. These are used by
+  // TypeTemplates.
+  bool ResolveParamAsType(const flat::TypeTemplate* layout,
+                          const std::unique_ptr<LayoutParameter>& param,
+                          const Type** out_type) const;
+  bool ResolveParamAsSize(const flat::TypeTemplate* layout,
+                          const std::unique_ptr<LayoutParameter>& param,
+                          const Size** out_size) const;
+
+  // Top level methods for resolving constraints. These are used by Types
+  enum class ConstraintKind {
+    kHandleSubtype,
+    kHandleRights,
+    kSize,
+    kNullability,
+  };
+  struct ResolvedConstraint {
+    ConstraintKind kind;
+
+    union Value {
+      uint32_t handle_subtype;
+      const HandleRights* handle_rights;
+      const Size* size;
+      // Storing a value for nullability is redundant, since there's only one possible value - if we
+      // resolved to optional, then the caller knows that the resulting value is
+      // types::Nullability::kNullable.
+    } value;
+  };
+  // Convenience method to iterate through the possible interpretations, returning the first one
+  // that succeeds. This is valid because the interpretations are mutually exclusive, since a Name
+  // can only ever refer to one kind of thing.
+  bool ResolveConstraintAs(const std::unique_ptr<Constant>& constraint,
+                           const std::vector<ConstraintKind>& interpretations,
+                           Resource* resource_decl, ResolvedConstraint* out) const;
+
+  // These methods forward their implementation to the library_. They are used
+  // by the top level methods above, as well as directly by ResolveOldSyntaxArgs
   bool ResolveType(TypeConstructorOld* type) const;
+  bool ResolveType(TypeConstructorNew* type) const;
   bool ResolveSizeBound(Constant* size_constant, const Size** out_size) const;
   bool ResolveAsOptional(Constant* constant) const;
   bool ResolveAsHandleSubtype(Resource* resource, const std::unique_ptr<Constant>& constant,
@@ -562,12 +784,16 @@
   bool ResolveAsHandleRights(Resource* resource, Constant* constant,
                              const HandleRights** out_rights) const;
 
+  template <typename... Args>
+  bool Fail(const ErrorDef<Args...>& err, const std::optional<SourceSpan>& span,
+            const Args&... args) const;
+
   // This is unrelated to resolving arguments: it is required in the workaround for
   // the special handling of handles, and can be removed once resources are fully
   // generalized (see HandleTypeTemplate::GetResource)
   Decl* LookupDeclByName(Name::Key name) const;
 
-  // Used specificaly in TypeAliasTypeTemplates to recursively compile the next
+  // Used specifically in TypeAliasTypeTemplates to recursively compile the next
   // type alias.
   bool CompileDecl(Decl* decl) const;
 
@@ -575,6 +801,30 @@
   Library* library_;
 };
 
+// CreateInvocation represents a set of resolved layout parameters/constraints.
+// It is only used in the old syntax path.
+struct CreateInvocation {
+  CreateInvocation(const Name& name, const Type* arg_type, std::optional<uint32_t> obj_type,
+                   std::optional<types::HandleSubtype> handle_subtype,
+                   const HandleRights* handle_rights, const Size* size,
+                   const types::Nullability nullability)
+      : name(name),
+        arg_type(arg_type),
+        obj_type(obj_type),
+        handle_subtype(handle_subtype),
+        handle_rights(handle_rights),
+        size(size),
+        nullability(nullability) {}
+
+  const Name& name;
+  const Type* arg_type;
+  std::optional<uint32_t> obj_type;
+  std::optional<types::HandleSubtype> handle_subtype;
+  const HandleRights* handle_rights;
+  const Size* size;
+  const types::Nullability nullability;
+};
+
 class TypeTemplate {
  public:
   TypeTemplate(Name name, Typespace* typespace, Reporter* reporter)
@@ -586,39 +836,11 @@
 
   const Name& name() const { return name_; }
 
-  struct CreateInvocation {
-    CreateInvocation(const Name& name, const Type* arg_type, std::optional<uint32_t> obj_type,
-                     std::optional<types::HandleSubtype> handle_subtype,
-                     const HandleRights* handle_rights, const Size* size,
-                     const types::Nullability nullability)
-        : name(name),
-          arg_type(arg_type),
-          obj_type(obj_type),
-          handle_subtype(handle_subtype),
-          handle_rights(handle_rights),
-          size(size),
-          nullability(nullability) {}
-
-    const Name& name;
-    const Type* arg_type;
-    std::optional<uint32_t> obj_type;
-    std::optional<types::HandleSubtype> handle_subtype;
-    const HandleRights* handle_rights;
-    const Size* size;
-    const types::Nullability nullability;
-  };
-
   // The set of unresolved layout arguments and constraints as they appear in the
-  // old syntax, i.e. coming directly from flat::TypeConstructorOld.
+  // old syntax, i.e. coming directly from flat::TypeConstructor.
   // Unlike in the more general form ArgsAndConstraintsNew, this representation
-  // can be resolved to the CreateInvocation before determining what the layout is.
-  struct ArgsAndConstraintsOld {
-    // Type templates should not need to know the name of the layout and should
-    // only require a span for reporting errors, since any logic related to the
-    // name itself should be encapsulated in the layout resolution process. However,
-    // currently an exception must be made for handles so that we can double check
-    // whether the name actual refers to a Resource at runtime (see
-    // TypeTemplate::GetResource for details).
+  // can be resolved to a CreateInvocation before determining what the layout is.
+  struct OldSyntaxParamsAndConstraints {
     const Name& name;
     const std::unique_ptr<TypeConstructorOld>& maybe_arg_type_ctor;
     const std::optional<Name>& handle_subtype_identifier;
@@ -626,14 +848,30 @@
     const std::unique_ptr<Constant>& maybe_size;
     const types::Nullability nullability;
   };
-  virtual bool Create(
-      const LibraryMediator& resolver, const ArgsAndConstraintsOld& unresolved_args,
-      std::unique_ptr<Type>* out_type,
-      std::optional<TypeConstructorOld::FromTypeAlias>* out_from_type_alias) const = 0;
-  bool ResolveArgs(const LibraryMediator& resolver, const ArgsAndConstraintsOld& unresolved_args,
-                   std::unique_ptr<CreateInvocation>* out_args) const;
 
-  virtual bool GetResource(const LibraryMediator& resolver, const Name& name,
+  virtual bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& args,
+                      std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const = 0;
+
+  bool ResolveOldSyntaxArgs(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& args,
+                            std::unique_ptr<CreateInvocation>* out_args,
+                            LayoutInvocation* out_params) const;
+
+  struct NewSyntaxParamsAndConstraints {
+    // Type templates should not need to know the name of the layout and should
+    // only require a span for reporting errors, since any logic related to the
+    // name itself should be encapsulated in the layout resolution process. However,
+    // currently an exception must be made for handles so that we can double check
+    // whether the name actual refers to a Resource at runtime (see
+    // TypeTemplate::GetResource for details).
+    const Name& name;
+    const std::unique_ptr<LayoutParameterList>& parameters;
+    const std::unique_ptr<TypeConstraints>& constraints;
+  };
+
+  virtual bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& args,
+                      std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const = 0;
+
+  virtual bool GetResource(const LibraryMediator& lib, const Name& name,
                            Resource** out_resource) const;
 
  protected:
@@ -664,8 +902,11 @@
               const std::optional<Name>& handle_subtype_identifier,
               const std::unique_ptr<Constant>& handle_rights,
               const std::unique_ptr<Constant>& maybe_size, types::Nullability nullability,
-              const Type** out_type,
-              std::optional<TypeConstructorOld::FromTypeAlias>* out_from_type_alias);
+              const Type** out_type, LayoutInvocation* out_params);
+  bool Create(const LibraryMediator& lib, const flat::Name& name,
+              const std::unique_ptr<LayoutParameterList>& parameters,
+              const std::unique_ptr<TypeConstraints>& constraints, const Type** out_type,
+              LayoutInvocation* out_params);
 
   void AddTemplate(std::unique_ptr<TypeTemplate> type_template);
 
@@ -687,8 +928,11 @@
                       const std::optional<Name>& handle_subtype_identifier,
                       const std::unique_ptr<Constant>& handle_rights,
                       const std::unique_ptr<Constant>& maybe_size, types::Nullability nullability,
-                      std::unique_ptr<Type>* out_type,
-                      std::optional<TypeConstructorOld::FromTypeAlias>* out_from_type_alias);
+                      std::unique_ptr<Type>* out_type, LayoutInvocation* out_params);
+  bool CreateNotOwned(const LibraryMediator& lib, const flat::Name& name,
+                      const std::unique_ptr<LayoutParameterList>& parameters,
+                      const std::unique_ptr<TypeConstraints>& constraints,
+                      std::unique_ptr<Type>* out_type, LayoutInvocation* out_params);
 
   std::map<Name::Key, std::unique_ptr<TypeTemplate>> templates_;
   std::vector<std::unique_ptr<Type>> types_;
@@ -909,38 +1153,36 @@
 
   bool ConsumeConstant(std::unique_ptr<raw::Constant> raw_constant,
                        std::unique_ptr<Constant>* out_constant);
+  void ConsumeLiteralConstant(raw::LiteralConstant* raw_constant,
+                              std::unique_ptr<LiteralConstant>* out_constant);
   bool ConsumeTypeConstructorOld(std::unique_ptr<raw::TypeConstructorOld> raw_type_ctor,
                                  std::unique_ptr<TypeConstructorOld>* out_type);
 
-  void ConsumeUsing(std::unique_ptr<raw::Using> using_directive, fidl::utils::Syntax syntax);
-  bool ConsumeTypeAlias(std::unique_ptr<raw::AliasDeclaration> alias_declaration,
-                        fidl::utils::Syntax syntax);
-  bool ConsumeTypeAlias(std::unique_ptr<raw::Using> using_directive, fidl::utils::Syntax syntax);
+  void ConsumeUsing(std::unique_ptr<raw::Using> using_directive);
+  bool ConsumeTypeAlias(std::unique_ptr<raw::AliasDeclaration> alias_declaration);
   void ConsumeBitsDeclaration(std::unique_ptr<raw::BitsDeclaration> bits_declaration);
   void ConsumeConstDeclaration(std::unique_ptr<raw::ConstDeclaration> const_declaration);
   void ConsumeEnumDeclaration(std::unique_ptr<raw::EnumDeclaration> enum_declaration);
-  void ConsumeProtocolDeclaration(std::unique_ptr<raw::ProtocolDeclaration> protocol_declaration,
-                                  fidl::utils::Syntax syntax);
-  bool ConsumeResourceDeclaration(std::unique_ptr<raw::ResourceDeclaration> resource_declaration,
-                                  fidl::utils::Syntax syntax);
+  void ConsumeProtocolDeclaration(std::unique_ptr<raw::ProtocolDeclaration> protocol_declaration);
+  bool ConsumeResourceDeclaration(std::unique_ptr<raw::ResourceDeclaration> resource_declaration);
   bool ConsumeParameterList(Name name, std::unique_ptr<raw::ParameterList> parameter_list,
-                            bool anonymous, fidl::utils::Syntax syntax, Struct** out_struct_decl);
+                            bool anonymous, Struct** out_struct_decl);
   bool CreateMethodResult(const Name& protocol_name, SourceSpan response_span,
-                          raw::ProtocolMethod* method, Struct* in_response,
-                          fidl::utils::Syntax syntax, Struct** out_response);
-  void ConsumeServiceDeclaration(std::unique_ptr<raw::ServiceDeclaration> service_decl,
-                                 fidl::utils::Syntax syntax);
+                          raw::ProtocolMethod* method, Struct* in_response, Struct** out_response);
+  void ConsumeServiceDeclaration(std::unique_ptr<raw::ServiceDeclaration> service_decl);
   void ConsumeStructDeclaration(std::unique_ptr<raw::StructDeclaration> struct_declaration);
   void ConsumeTableDeclaration(std::unique_ptr<raw::TableDeclaration> table_declaration);
   void ConsumeUnionDeclaration(std::unique_ptr<raw::UnionDeclaration> union_declaration);
 
   // start new syntax
   void ConsumeTypeDecl(std::unique_ptr<raw::TypeDecl> type_decl);
+  // TODO(fxbug.dev/74683): The context parameter is currently unused, but exists
+  // to help generate a name when implementing anonymous layouts
   bool ConsumeTypeConstructorNew(std::unique_ptr<raw::TypeConstructorNew> raw_type_ctor,
                                  const Name& context,
-                                 std::unique_ptr<TypeConstructorOld>* out_type);
+                                 std::unique_ptr<TypeConstructorNew>* out_type);
   bool ConsumeTypeConstructor(raw::TypeConstructor raw_type_ctor, const Name& context,
-                              std::unique_ptr<TypeConstructorOld>* out_type);
+                              TypeConstructor* out_type);
 
   // Here, T is expected to be an ordinal-carrying flat AST class (ie, Table or
   // Union), while M is its "Member" sub-class.
@@ -953,8 +1195,6 @@
   template <typename T, typename M>
   bool ConsumeValueLayout(std::unique_ptr<raw::Layout>, const Name&);
   bool ConsumeLayout(std::unique_ptr<raw::Layout>, const Name&);
-  bool IsOptionalConstraint(std::unique_ptr<TypeConstructorOld>&,
-                            const std::unique_ptr<raw::Constant>&);
   // end new syntax
 
   bool TypeCanBeConst(const Type* type);
@@ -964,8 +1204,7 @@
   bool ResolveAsOptional(Constant* constant) const;
   bool TypeIsConvertibleTo(const Type* from_type, const Type* to_type);
   std::unique_ptr<TypeConstructorOld> IdentifierTypeForDecl(const Decl* decl,
-                                                            types::Nullability nullability,
-                                                            fidl::utils::Syntax syntax);
+                                                            types::Nullability nullability);
 
   bool AddConstantDependencies(const Constant* constant, std::set<const Decl*>* out_edges);
   bool DeclDependencies(const Decl* decl, std::set<const Decl*>* out_edges);
@@ -983,31 +1222,22 @@
   bool CompileUnion(Union* union_declaration);
   bool CompileTypeAlias(TypeAlias* type_alias);
 
-  // Compiling a type validates the type: in particular, we validate that optional identifier types
-  // refer to things that can in fact be nullable (ie not enums).
-  //
-  // These three sets of functions functions exist to support differing behavior
-  // between the "old" and "new" syntax: protocols can be treated as types in the
-  // former but not the latter.
-
   // This top level function switches behavior depending on what syntax is
-  // being read, calling into CompileTypeConstructorAllowing to do the actual
-  // compilation.
-  bool CompileTypeConstructor(TypeConstructorOld* type);
-  // This version compiles the constructor, then validates that it is of the
-  // expected "category". The possible "categories" are the different classes of things
-  // that are stored in the Typespace: types, protocols, and services - things
-  // of one category cannot generally be used in place of another.
+  // being read
+  bool CompileTypeConstructor(TypeConstructor* type_ctor);
+  bool CompileTypeConstructorOld(TypeConstructorOld* type_ctor);
+  bool CompileTypeConstructorNew(TypeConstructorNew* type_ctor);
+
   enum class AllowedCategories {
     kTypeOrProtocol,
     kTypeOnly,
     kProtocolOnly,
     // Note: there's currently no scenario where we expect a service.
   };
-  // Compiles the TypeConstructor and then validates that it's in one of the
-  // allowed categories.
-  bool CompileTypeConstructorAllowing(TypeConstructorOld* type, AllowedCategories category);
-  bool VerifyTypeCategory(TypeConstructorOld* type, AllowedCategories category);
+  // Returns true if the provided type falls into one of the specified categories,
+  // and false otherwise. A span can be provided for error reporting.
+  bool VerifyTypeCategory(const Type* type, std::optional<SourceSpan> span,
+                          AllowedCategories category);
 
   ConstantValue::Kind ConstantValuePrimitiveKind(const types::PrimitiveSubtype primitive_subtype);
   bool ResolveHandleRightsConstant(Resource* resource, Constant* constant,
@@ -1139,32 +1369,31 @@
 // syntax inside the implementation.
 class ConsumeStep : public StepBase {
  public:
-  ConsumeStep(Library* library, fidl::utils::Syntax syntax) : StepBase(library), syntax(syntax) {}
+  ConsumeStep(Library* library, fidl::utils::Syntax syntax) : StepBase(library) {}
 
   void ForAliasDeclaration(std::unique_ptr<raw::AliasDeclaration> alias_declaration) {
-    library_->ConsumeTypeAlias(std::move(alias_declaration), syntax);
+    library_->ConsumeTypeAlias(std::move(alias_declaration));
   }
   void ForUsing(std::unique_ptr<raw::Using> using_directive) {
-    library_->ConsumeUsing(std::move(using_directive), syntax);
+    library_->ConsumeUsing(std::move(using_directive));
   }
   void ForBitsDeclaration(std::unique_ptr<raw::BitsDeclaration> bits_declaration) {
     library_->ConsumeBitsDeclaration(std::move(bits_declaration));
   }
   void ForConstDeclaration(std::unique_ptr<raw::ConstDeclaration> const_declaration) {
-    // TODO(fxbug.dev/71178): pipe syntax through
     library_->ConsumeConstDeclaration(std::move(const_declaration));
   }
   void ForEnumDeclaration(std::unique_ptr<raw::EnumDeclaration> enum_declaration) {
     library_->ConsumeEnumDeclaration(std::move(enum_declaration));
   }
   void ForProtocolDeclaration(std::unique_ptr<raw::ProtocolDeclaration> protocol_declaration) {
-    library_->ConsumeProtocolDeclaration(std::move(protocol_declaration), syntax);
+    library_->ConsumeProtocolDeclaration(std::move(protocol_declaration));
   }
   void ForResourceDeclaration(std::unique_ptr<raw::ResourceDeclaration> resource_declaration) {
-    library_->ConsumeResourceDeclaration(std::move(resource_declaration), syntax);
+    library_->ConsumeResourceDeclaration(std::move(resource_declaration));
   }
   void ForServiceDeclaration(std::unique_ptr<raw::ServiceDeclaration> service_decl) {
-    library_->ConsumeServiceDeclaration(std::move(service_decl), syntax);
+    library_->ConsumeServiceDeclaration(std::move(service_decl));
   }
   void ForStructDeclaration(std::unique_ptr<raw::StructDeclaration> struct_declaration) {
     library_->ConsumeStructDeclaration(std::move(struct_declaration));
@@ -1178,8 +1407,6 @@
   void ForTypeDecl(std::unique_ptr<raw::TypeDecl> type_decl) {
     library_->ConsumeTypeDecl(std::move(type_decl));
   }
-
-  fidl::utils::Syntax syntax;
 };
 
 class CompileStep : public StepBase {
diff --git a/tools/fidl/fidlc/include/fidl/json_generator.h b/tools/fidl/fidlc/include/fidl/json_generator.h
index 45891b8..89a5602 100644
--- a/tools/fidl/fidlc/include/fidl/json_generator.h
+++ b/tools/fidl/fidlc/include/fidl/json_generator.h
@@ -127,8 +127,12 @@
   void Generate(const flat::Table::Member& value);
   void Generate(const flat::Union& value);
   void Generate(const flat::Union::Member& value);
-  void Generate(const flat::TypeConstructorOld::FromTypeAlias& value);
-  void Generate(const flat::TypeConstructorOld& value);
+  void Generate(const flat::LayoutInvocation& value);
+  // A TypeConstructorPtr is a variant of pointers, so just calling Generate on
+  // a type constructor pointer will first try to generate as a bool. A different
+  // name is picked to be unambiguous.
+  void GenerateTypeCtor(const flat::TypeConstructorPtr& value);
+  void Generate(const flat::TypeConstructor& value);
   void Generate(const flat::TypeAlias& value);
   void Generate(const flat::Library* library);
 
@@ -137,10 +141,9 @@
     kConcrete,
     kParameterized,
   };
-  void GenerateTypeAndFromTypeAlias(TypeKind parent_type_kind,
-                                    const flat::TypeConstructorOld& value,
+  void GenerateTypeAndFromTypeAlias(TypeKind parent_type_kind, flat::TypeConstructorPtr value,
                                     Position position = Position::kSubsequent);
-  void GenerateTypeAndFromTypeAlias(const flat::TypeConstructorOld& value,
+  void GenerateTypeAndFromTypeAlias(const flat::TypeConstructor& value,
                                     Position position = Position::kSubsequent);
 
   // This is a generator for the builtin generics: array, vector, and request.
@@ -158,9 +161,9 @@
   // be a nullable vector of size 5, but the de-aliased constructor passed in
   // will be the underlying type for just Foo, in this is case "vector<bool:5>."
   void GenerateParameterizedType(TypeKind parent_type_kind, const flat::Type* type,
-                                 const flat::TypeConstructorOld& value,
+                                 flat::TypeConstructorPtr type_ctor,
                                  Position position = Position::kSubsequent);
-  void GenerateExperimentalMaybeFromTypeAlias(const flat::TypeConstructorOld& value);
+  void GenerateExperimentalMaybeFromTypeAlias(const flat::LayoutInvocation& invocation);
   void GenerateRequest(const std::string& prefix, const flat::Struct& value);
   void GenerateDeclarationsEntry(int count, const flat::Name& name, std::string_view decl_kind);
   void GenerateDeclarationsMember(const flat::Library* library,
diff --git a/tools/fidl/fidlc/lib/c_generator.cc b/tools/fidl/fidlc/lib/c_generator.cc
index ce62e848..2c38c45 100644
--- a/tools/fidl/fidlc/lib/c_generator.cc
+++ b/tools/fidl/fidlc/lib/c_generator.cc
@@ -141,7 +141,7 @@
     return true;
   }
   for (const auto& member : args->members) {
-    if (!TypeAllowed(library, member.type_ctor->type)) {
+    if (!TypeAllowed(library, flat::GetType(member.type_ctor))) {
       return false;
     }
   }
@@ -689,7 +689,7 @@
 CGenerator::Member CreateMember(const flat::Library* library, const T& decl,
                                 bool* out_allowed = nullptr) {
   std::string name = NameIdentifier(decl.name);
-  const flat::Type* type = decl.type_ctor->type;
+  const flat::Type* type = flat::GetType(decl.type_ctor);
   auto decl_kind = GetDeclKind(library, type);
   auto type_name = NameFlatCType(type, decl_kind);
   std::string element_type_name;
@@ -1008,7 +1008,8 @@
 
 void CGenerator::ProduceBitsForwardDeclaration(const NamedBits& named_bits) {
   auto subtype =
-      static_cast<const flat::PrimitiveType*>(named_bits.bits_info.subtype_ctor->type)->subtype;
+      static_cast<const flat::PrimitiveType*>(flat::GetType(named_bits.bits_info.subtype_ctor))
+          ->subtype;
   GenerateIntegerTypedef(subtype, named_bits.name);
   for (const auto& member : named_bits.bits_info.members) {
     std::string member_name = named_bits.name + "_" + NameIdentifier(member.name);
@@ -1078,10 +1079,11 @@
     return;
   }
 
-  switch (ci.type_ctor->type->kind) {
+  switch (flat::GetType(ci.type_ctor)->kind) {
     case flat::Type::Kind::kPrimitive:
       GeneratePrimitiveDefine(
-          named_const.name, static_cast<const flat::PrimitiveType*>(ci.type_ctor->type)->subtype,
+          named_const.name,
+          static_cast<const flat::PrimitiveType*>(flat::GetType(ci.type_ctor))->subtype,
           static_cast<flat::LiteralConstant*>(ci.value.get())->literal->span().data());
       break;
     case flat::Type::Kind::kString:
diff --git a/tools/fidl/fidlc/lib/coded_types_generator.cc b/tools/fidl/fidlc/lib/coded_types_generator.cc
index e353d4b..bce2a9c 100644
--- a/tools/fidl/fidlc/lib/coded_types_generator.cc
+++ b/tools/fidl/fidlc/lib/coded_types_generator.cc
@@ -22,7 +22,7 @@
 
 CodedTypesGenerator::FlattenedStructMember::FlattenedStructMember(const flat::StructMember& member,
                                                                   const WireFormat wire_format)
-    : type(member.type_ctor->type),
+    : type(flat::GetType(member.type_ctor)),
       name(member.name),
       inline_size(member.typeshape(wire_format).InlineSize()),
       offset(member.fieldshape(wire_format).Offset()),
@@ -31,10 +31,10 @@
 std::vector<CodedTypesGenerator::FlattenedStructMember> CodedTypesGenerator::FlattenedStructMembers(
     const flat::Struct& input, const WireFormat wire_format) {
   auto get_struct_decl = [](const flat::StructMember& member) -> const flat::Struct* {
-    if (member.type_ctor->nullability == types::Nullability::kNullable) {
+    if (flat::GetType(member.type_ctor)->nullability == types::Nullability::kNullable) {
       return nullptr;
     }
-    const flat::Type* type = member.type_ctor->type;
+    const flat::Type* type = flat::GetType(member.type_ctor);
     if (type->kind != flat::Type::Kind::kIdentifier) {
       return nullptr;
     }
@@ -354,7 +354,7 @@
             }
             if (member.maybe_used) {
               const auto* coded_member_type =
-                  CompileType(member.maybe_used->type_ctor->type,
+                  CompileType(flat::GetType(member.maybe_used->type_ctor),
                               coded::CodingContext::kInsideEnvelope, wire_format);
               coded_xunion->fields.emplace_back(coded_member_type);
               nullable_coded_xunion->fields.emplace_back(coded_member_type);
@@ -385,7 +385,7 @@
           continue;
         std::string member_name =
             coded_table->coded_name + "_" + std::string(member.maybe_used->name.data());
-        auto coded_member_type = CompileType(member.maybe_used->type_ctor->type,
+        auto coded_member_type = CompileType(flat::GetType(member.maybe_used->type_ctor),
                                              coded::CodingContext::kInsideEnvelope, wire_format);
         table_fields.emplace_back(coded_member_type, member.ordinal->value);
       }
@@ -402,7 +402,8 @@
     case flat::Decl::Kind::kBits: {
       auto bits_decl = static_cast<const flat::Bits*>(decl);
       std::string bits_name = NameCodedName(bits_decl->name);
-      auto primitive_type = static_cast<const flat::PrimitiveType*>(bits_decl->subtype_ctor->type);
+      auto primitive_type =
+          static_cast<const flat::PrimitiveType*>(flat::GetType(bits_decl->subtype_ctor));
       named_coded_types_.emplace(
           bits_decl->name, std::make_unique<coded::BitsType>(
                                std::move(bits_name), primitive_type->subtype,
diff --git a/tools/fidl/fidlc/lib/flat_ast.cc b/tools/fidl/fidlc/lib/flat_ast.cc
index 241ff5e..9cb038e 100644
--- a/tools/fidl/fidlc/lib/flat_ast.cc
+++ b/tools/fidl/fidlc/lib/flat_ast.cc
@@ -16,6 +16,7 @@
 #include "fidl/diagnostic_types.h"
 #include "fidl/diagnostics.h"
 #include "fidl/experimental_flags.h"
+#include "fidl/flat/types.h"
 #include "fidl/lexer.h"
 #include "fidl/names.h"
 #include "fidl/ordinals.h"
@@ -178,6 +179,18 @@
 
 }  // namespace
 
+TypeConstructorPtr GetTypeCtorAsPtr(const TypeConstructor& type_ctor) {
+  return std::visit(fidl::utils::matchers{
+                        [](const std::unique_ptr<TypeConstructorOld>& e) -> TypeConstructorPtr {
+                          return e.get();
+                        },
+                        [](const std::unique_ptr<TypeConstructorNew>& e) -> TypeConstructorPtr {
+                          return e.get();
+                        },
+                    },
+                    type_ctor);
+}
+
 uint32_t PrimitiveType::SubtypeSize(types::PrimitiveSubtype subtype) {
   switch (subtype) {
     case types::PrimitiveSubtype::kBool:
@@ -309,24 +322,35 @@
                        const std::optional<Name>& handle_subtype_identifier,
                        const std::unique_ptr<Constant>& handle_rights,
                        const std::unique_ptr<Constant>& maybe_size, types::Nullability nullability,
-                       const Type** out_type,
-                       std::optional<TypeConstructorOld::FromTypeAlias>* out_from_type_alias) {
+                       const Type** out_type, LayoutInvocation* out_params) {
   std::unique_ptr<Type> type;
   if (!CreateNotOwned(lib, name, maybe_arg_type_ctor, handle_subtype_identifier, handle_rights,
-                      maybe_size, nullability, &type, out_from_type_alias))
+                      maybe_size, nullability, &type, out_params))
     return false;
   types_.push_back(std::move(type));
   *out_type = types_.back().get();
   return true;
 }
 
-bool Typespace::CreateNotOwned(
-    const LibraryMediator& lib, const flat::Name& name,
-    const std::unique_ptr<TypeConstructorOld>& maybe_arg_type_ctor,
-    const std::optional<Name>& handle_subtype_identifier,
-    const std::unique_ptr<Constant>& handle_rights, const std::unique_ptr<Constant>& maybe_size,
-    types::Nullability nullability, std::unique_ptr<Type>* out_type,
-    std::optional<TypeConstructorOld::FromTypeAlias>* out_from_type_alias) {
+bool Typespace::Create(const LibraryMediator& lib, const flat::Name& name,
+                       const std::unique_ptr<LayoutParameterList>& parameters,
+                       const std::unique_ptr<TypeConstraints>& constraints, const Type** out_type,
+                       LayoutInvocation* out_params) {
+  std::unique_ptr<Type> type;
+  if (!CreateNotOwned(lib, name, parameters, constraints, &type, out_params))
+    return false;
+  types_.push_back(std::move(type));
+  *out_type = types_.back().get();
+  return true;
+}
+
+bool Typespace::CreateNotOwned(const LibraryMediator& lib, const flat::Name& name,
+                               const std::unique_ptr<TypeConstructorOld>& maybe_arg_type_ctor,
+                               const std::optional<Name>& handle_subtype_identifier,
+                               const std::unique_ptr<Constant>& handle_rights,
+                               const std::unique_ptr<Constant>& maybe_size,
+                               types::Nullability nullability, std::unique_ptr<Type>* out_type,
+                               LayoutInvocation* out_params) {
   // TODO(pascallouis): lookup whether we've already created the type, and
   // return it rather than create a new one. Lookup must be by name,
   // arg_type, size, and nullability.
@@ -343,7 +367,25 @@
                                 .handle_rights = handle_rights,
                                 .maybe_size = maybe_size,
                                 .nullability = nullability},
-                               out_type, out_from_type_alias);
+                               out_type, out_params);
+}
+
+bool Typespace::CreateNotOwned(const LibraryMediator& lib, const flat::Name& name,
+                               const std::unique_ptr<LayoutParameterList>& parameters,
+                               const std::unique_ptr<TypeConstraints>& constraints,
+                               std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) {
+  // TODO(pascallouis): lookup whether we've already created the type, and
+  // return it rather than create a new one. Lookup must be by name,
+  // arg_type, size, and nullability.
+
+  auto type_template = LookupTemplate(name);
+  if (type_template == nullptr) {
+    reporter_->Report(ErrUnknownType, name.span(), name);
+    return false;
+  }
+  return type_template->Create(lib,
+                               {.name = name, .parameters = parameters, .constraints = constraints},
+                               out_type, out_params);
 }
 
 void Typespace::AddTemplate(std::unique_ptr<TypeTemplate> type_template) {
@@ -376,24 +418,30 @@
   return false;
 }
 
-bool TypeTemplate::ResolveArgs(const LibraryMediator& lib,
-                               const ArgsAndConstraintsOld& unresolved_args,
-                               std::unique_ptr<TypeTemplate::CreateInvocation>* out_args) const {
+bool TypeTemplate::ResolveOldSyntaxArgs(const LibraryMediator& lib,
+                                        const OldSyntaxParamsAndConstraints& unresolved_args,
+                                        std::unique_ptr<CreateInvocation>* out_args,
+                                        LayoutInvocation* out_params) const {
   const Type* maybe_arg_type = nullptr;
   if (unresolved_args.maybe_arg_type_ctor != nullptr) {
     if (!lib.ResolveType(unresolved_args.maybe_arg_type_ctor.get()))
       return false;
     maybe_arg_type = unresolved_args.maybe_arg_type_ctor->type;
+    out_params->element_type_resolved = maybe_arg_type;
+    out_params->element_type_raw = unresolved_args.maybe_arg_type_ctor.get();
   }
 
   const Size* size = nullptr;
   if (unresolved_args.maybe_size != nullptr) {
     if (!lib.ResolveSizeBound(unresolved_args.maybe_size.get(), &size)) {
+      reporter_->Report(ErrCouldNotParseSizeBound, unresolved_args.maybe_size->span);
       return false;
     }
+    out_params->size_resolved = size;
+    out_params->size_raw = unresolved_args.maybe_size.get();
   }
 
-  Resource* handle_resource_decl;
+  Resource* handle_resource_decl = nullptr;
   if (unresolved_args.handle_subtype_identifier || unresolved_args.handle_rights) {
     if (!GetResource(lib, unresolved_args.name, &handle_resource_decl))
       return false;
@@ -420,6 +468,7 @@
       return Fail(ErrCouldNotResolveHandleSubtype, name);
     obj_type = raw_obj_type;
     handle_subtype = types::HandleSubtype(raw_obj_type);
+    out_params->subtype_resolved = raw_obj_type;
   }
 
   const HandleRights* rights = nullptr;
@@ -427,10 +476,13 @@
     if (!lib.ResolveAsHandleRights(handle_resource_decl, unresolved_args.handle_rights.get(),
                                    &rights))
       return Fail(ErrCouldNotResolveHandleRights);
+    out_params->rights_resolved = rights;
+    out_params->rights_raw = unresolved_args.handle_rights.get();
   }
 
   // No work needed for nullability - in the old syntax there's nothing to resolve
   // because ? always indicates nullable.
+  out_params->nullability = unresolved_args.nullability;
 
   *out_args =
       std::make_unique<CreateInvocation>(unresolved_args.name, maybe_arg_type, obj_type,
@@ -452,11 +504,22 @@
                         types::PrimitiveSubtype subtype)
       : TypeTemplate(Name::CreateIntrinsic(name), typespace, reporter), subtype_(subtype) {}
 
-  bool Create(const LibraryMediator& lib, const ArgsAndConstraintsOld& unresolved_args,
-              std::unique_ptr<Type>* out_type,
-              std::optional<TypeConstructorOld::FromTypeAlias>* out_from_type_alias) const {
+  bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args,
+              std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
+    size_t num_params = unresolved_args.parameters->items.size();
+    if (num_params != 0) {
+      return Fail(ErrWrongNumberOfLayoutParameters, unresolved_args.parameters->span, size_t(0),
+                  num_params);
+    }
+
+    PrimitiveType type(name_, subtype_);
+    return type.ApplyConstraints(lib, *unresolved_args.constraints, this, out_type, out_params);
+  }
+
+  bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args,
+              std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
     std::unique_ptr<CreateInvocation> args;
-    if (!ResolveArgs(lib, unresolved_args, &args))
+    if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params))
       return false;
 
     assert(!args->handle_subtype);
@@ -464,66 +527,77 @@
 
     if (args->arg_type != nullptr)
       return Fail(ErrCannotBeParameterized, args->name.span());
-    if (args->size != nullptr)
-      return Fail(ErrCannotHaveSize, args->name.span());
-    if (args->nullability == types::Nullability::kNullable)
-      return Fail(ErrCannotBeNullable, args->name.span());
 
-    *out_type = std::make_unique<PrimitiveType>(name_, subtype_);
-    return true;
+    PrimitiveType type(name_, subtype_);
+    return type.ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type, out_params);
   }
 
  private:
   const types::PrimitiveSubtype subtype_;
 };
 
-class BytesTypeTemplate final : public TypeTemplate {
- public:
-  BytesTypeTemplate(Typespace* typespace, Reporter* reporter)
-      : TypeTemplate(Name::CreateIntrinsic("vector"), typespace, reporter),
-        uint8_type_(kUint8Type) {}
+bool PrimitiveType::ApplyConstraints(const flat::LibraryMediator& lib,
+                                     const TypeConstraints& constraints, const TypeTemplate* layout,
+                                     std::unique_ptr<Type>* out_type,
+                                     LayoutInvocation* out_params) const {
+  size_t num_constraints = constraints.items.size();
+  // assume that a lone constraint was an attempt at specifying `optional` and provide a more
+  // specific error
+  // TOOD(fxbug.dev/75112): actually try to compile the optional constraint
+  if (num_constraints == 1)
+    return lib.Fail(ErrCannotBeNullable, constraints.items[0]->span, layout);
+  if (num_constraints > 1)
+    return lib.Fail(ErrTooManyConstraints, constraints.span, layout, size_t(0), num_constraints);
+  *out_type = std::make_unique<PrimitiveType>(name, subtype);
+  return true;
+}
 
-  bool Create(const LibraryMediator& lib, const ArgsAndConstraintsOld& unresolved_args,
-              std::unique_ptr<Type>* out_type,
-              std::optional<TypeConstructorOld::FromTypeAlias>* out_from_type_alias) const {
-    std::unique_ptr<CreateInvocation> args;
-    if (!ResolveArgs(lib, unresolved_args, &args))
-      return false;
-
-    assert(!args->handle_subtype);
-    assert(!args->handle_rights);
-
-    if (args->arg_type != nullptr)
-      return Fail(ErrCannotBeParameterized, args->name.span());
-    const Size* size = args->size;
-    if (size == nullptr)
-      size = &kMaxSize;
-
-    *out_type = std::make_unique<VectorType>(name_, &uint8_type_, size, args->nullability);
-    return true;
-  }
-
- private:
-  // TODO(fxbug.dev/7724): Remove when canonicalizing types.
-  const Name kUint8TypeName = Name::CreateIntrinsic("uint8");
-  const PrimitiveType kUint8Type = PrimitiveType(kUint8TypeName, types::PrimitiveSubtype::kUint8);
-
-  const PrimitiveType uint8_type_;
-  const static Size kMaxSize;
-};
-
-const Size BytesTypeTemplate::kMaxSize = Size::Max();
+bool PrimitiveType::ApplySomeLayoutParametersAndConstraints(
+    const LibraryMediator& lib, const CreateInvocation& create_invocation,
+    const TypeTemplate* layout, std::unique_ptr<Type>* out_type,
+    LayoutInvocation* out_params) const {
+  if (create_invocation.size != nullptr)
+    return lib.Fail(ErrCannotHaveSize, create_invocation.name.span(), layout);
+  if (create_invocation.nullability == types::Nullability::kNullable)
+    return lib.Fail(ErrCannotBeNullable, create_invocation.name.span(), layout);
+  *out_type = std::make_unique<PrimitiveType>(name, subtype);
+  return true;
+}
 
 class ArrayTypeTemplate final : public TypeTemplate {
  public:
   ArrayTypeTemplate(Typespace* typespace, Reporter* reporter)
       : TypeTemplate(Name::CreateIntrinsic("array"), typespace, reporter) {}
 
-  bool Create(const LibraryMediator& lib, const ArgsAndConstraintsOld& unresolved_args,
-              std::unique_ptr<Type>* out_type,
-              std::optional<TypeConstructorOld::FromTypeAlias>* out_from_type_alias) const {
+  bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args,
+              std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
+    size_t num_params = unresolved_args.parameters->items.size();
+    size_t expected_params = 2;
+    if (num_params != expected_params) {
+      return Fail(ErrWrongNumberOfLayoutParameters, unresolved_args.parameters->span,
+                  expected_params, num_params);
+    }
+
+    const Type* element_type = nullptr;
+    if (!lib.ResolveParamAsType(this, unresolved_args.parameters->items[0], &element_type))
+      return false;
+    out_params->element_type_resolved = element_type;
+    out_params->element_type_raw = unresolved_args.parameters->items[0]->AsTypeCtor();
+
+    const Size* size = nullptr;
+    if (!lib.ResolveParamAsSize(this, unresolved_args.parameters->items[1], &size))
+      return false;
+    out_params->size_resolved = size;
+    out_params->size_raw = unresolved_args.parameters->items[1]->AsConstant();
+
+    ArrayType type(name_, element_type, size);
+    return type.ApplyConstraints(lib, *unresolved_args.constraints, this, out_type, out_params);
+  }
+
+  bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args,
+              std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
     std::unique_ptr<CreateInvocation> args;
-    if (!ResolveArgs(lib, unresolved_args, &args))
+    if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params))
       return false;
 
     assert(!args->handle_subtype);
@@ -535,55 +609,63 @@
       return Fail(ErrMustHaveSize, args->name.span());
     if (args->size->value == 0)
       return Fail(ErrMustHaveNonZeroSize, args->name.span());
-    if (args->nullability == types::Nullability::kNullable)
-      return Fail(ErrCannotBeNullable, args->name.span());
 
-    *out_type = std::make_unique<ArrayType>(name_, args->arg_type, args->size);
-    return true;
+    ArrayType type(name_, args->arg_type, args->size);
+    return type.ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type, out_params);
   }
 };
 
-class VectorTypeTemplate final : public TypeTemplate {
+bool ArrayType::ApplyConstraints(const flat::LibraryMediator& lib,
+                                 const TypeConstraints& constraints, const TypeTemplate* layout,
+                                 std::unique_ptr<Type>* out_type,
+                                 LayoutInvocation* out_params) const {
+  size_t num_constraints = constraints.items.size();
+  // assume that a lone constraint was an attempt at specifying `optional` and provide a more
+  // specific error
+  // TOOD(fxbug.dev/75112): actually try to compile the optional constraint
+  if (num_constraints == 1)
+    return lib.Fail(ErrCannotBeNullable, constraints.items[0]->span, layout);
+  if (num_constraints > 1)
+    return lib.Fail(ErrTooManyConstraints, constraints.span, layout, size_t(0), num_constraints);
+  *out_type = std::make_unique<ArrayType>(name, element_type, element_count);
+  return true;
+}
+
+bool ArrayType::ApplySomeLayoutParametersAndConstraints(const LibraryMediator& lib,
+                                                        const CreateInvocation& create_invocation,
+                                                        const TypeTemplate* layout,
+                                                        std::unique_ptr<Type>* out_type,
+                                                        LayoutInvocation* out_params) const {
+  if (create_invocation.size && create_invocation.size != element_count)
+    return lib.Fail(ErrCannotParameterizeAlias, create_invocation.name.span(), layout);
+  if (create_invocation.nullability == types::Nullability::kNullable)
+    return lib.Fail(ErrCannotBeNullable, create_invocation.name.span(), layout);
+  *out_type = std::make_unique<ArrayType>(name, element_type, element_count);
+  return true;
+}
+
+class BytesTypeTemplate final : public TypeTemplate {
  public:
-  VectorTypeTemplate(Typespace* typespace, Reporter* reporter)
-      : TypeTemplate(Name::CreateIntrinsic("vector"), typespace, reporter) {}
+  BytesTypeTemplate(Typespace* typespace, Reporter* reporter)
+      : TypeTemplate(Name::CreateIntrinsic("vector"), typespace, reporter),
+        uint8_type_(kUint8Type) {}
 
-  bool Create(const LibraryMediator& lib, const ArgsAndConstraintsOld& unresolved_args,
-              std::unique_ptr<Type>* out_type,
-              std::optional<TypeConstructorOld::FromTypeAlias>* out_from_type_alias) const {
-    std::unique_ptr<CreateInvocation> args;
-    if (!ResolveArgs(lib, unresolved_args, &args))
-      return false;
+  bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args,
+              std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
+    size_t num_params = unresolved_args.parameters->items.size();
+    if (num_params != 0) {
+      return Fail(ErrWrongNumberOfLayoutParameters, unresolved_args.parameters->span, size_t(0),
+                  num_params);
+    }
 
-    assert(!args->handle_subtype);
-    assert(!args->handle_rights);
-
-    if (args->arg_type == nullptr)
-      return Fail(ErrMustBeParameterized, args->name.span());
-    const Size* size = args->size;
-    if (size == nullptr)
-      size = &kMaxSize;
-
-    *out_type = std::make_unique<VectorType>(name_, args->arg_type, size, args->nullability);
-    return true;
+    VectorType type(name_, &uint8_type_);
+    return type.ApplyConstraints(lib, *unresolved_args.constraints, this, out_type, out_params);
   }
 
- private:
-  const static Size kMaxSize;
-};
-
-const Size VectorTypeTemplate::kMaxSize = Size::Max();
-
-class StringTypeTemplate final : public TypeTemplate {
- public:
-  StringTypeTemplate(Typespace* typespace, Reporter* reporter)
-      : TypeTemplate(Name::CreateIntrinsic("string"), typespace, reporter) {}
-
-  bool Create(const LibraryMediator& lib, const ArgsAndConstraintsOld& unresolved_args,
-              std::unique_ptr<Type>* out_type,
-              std::optional<TypeConstructorOld::FromTypeAlias>* out_from_type_alias) const {
+  bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args,
+              std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
     std::unique_ptr<CreateInvocation> args;
-    if (!ResolveArgs(lib, unresolved_args, &args))
+    if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params))
       return false;
 
     assert(!args->handle_subtype);
@@ -591,65 +673,241 @@
 
     if (args->arg_type != nullptr)
       return Fail(ErrCannotBeParameterized, args->name.span());
-    const Size* size = args->size;
-    if (size == nullptr)
-      size = &kMaxSize;
 
-    *out_type = std::make_unique<StringType>(name_, size, args->nullability);
-    return true;
+    VectorType type(name_, &uint8_type_);
+    return type.ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type, out_params);
   }
 
  private:
-  const static Size kMaxSize;
+  // TODO(fxbug.dev/7724): Remove when canonicalizing types.
+  const Name kUint8TypeName = Name::CreateIntrinsic("uint8");
+  const PrimitiveType kUint8Type = PrimitiveType(kUint8TypeName, types::PrimitiveSubtype::kUint8);
+
+  const PrimitiveType uint8_type_;
 };
 
-const Size StringTypeTemplate::kMaxSize = Size::Max();
+bool VectorBaseType::ResolveSizeAndNullability(const LibraryMediator& lib,
+                                               const TypeConstraints& constraints,
+                                               const TypeTemplate* layout,
+                                               LayoutInvocation* out_params) {
+  size_t num_constraints = constraints.items.size();
+  if (num_constraints == 1) {
+    LibraryMediator::ResolvedConstraint resolved;
+    if (!lib.ResolveConstraintAs(
+            constraints.items[0],
+            {LibraryMediator::ConstraintKind::kSize, LibraryMediator::ConstraintKind::kNullability},
+            nullptr /* resource_decl */, &resolved))
+      return lib.Fail(ErrUnexpectedConstraint, constraints.items[0]->span, layout);
+    switch (resolved.kind) {
+      case LibraryMediator::ConstraintKind::kSize:
+        out_params->size_resolved = resolved.value.size;
+        out_params->size_raw = constraints.items[0].get();
+        break;
+      case LibraryMediator::ConstraintKind::kNullability:
+        out_params->nullability = types::Nullability::kNullable;
+        break;
+      default:
+        assert(false && "Compiler bug: resolved to wrong constraint kind");
+    }
+  } else if (num_constraints == 2) {
+    // first constraint must be size, followed by optional
+    if (!lib.ResolveSizeBound(constraints.items[0].get(), &out_params->size_resolved))
+      return lib.Fail(ErrCouldNotParseSizeBound, std::nullopt);
+    out_params->size_raw = constraints.items[0].get();
+    if (!lib.ResolveAsOptional(constraints.items[1].get())) {
+      return lib.Fail(ErrUnexpectedConstraint, constraints.items[1]->span, layout);
+    }
+    out_params->nullability = types::Nullability::kNullable;
+  } else if (num_constraints >= 3) {
+    return lib.Fail(ErrTooManyConstraints, constraints.span, layout, size_t(2), num_constraints);
+  }
+  return true;
+}
+
+const Size VectorBaseType::kMaxSize = Size::Max();
+
+class VectorTypeTemplate final : public TypeTemplate {
+ public:
+  VectorTypeTemplate(Typespace* typespace, Reporter* reporter)
+      : TypeTemplate(Name::CreateIntrinsic("vector"), typespace, reporter) {}
+
+  bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args,
+              std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
+    size_t num_params = unresolved_args.parameters->items.size();
+    if (num_params != 1) {
+      return Fail(ErrWrongNumberOfLayoutParameters, unresolved_args.parameters->span, size_t(1),
+                  num_params);
+    }
+
+    const Type* element_type = nullptr;
+    if (!lib.ResolveParamAsType(this, unresolved_args.parameters->items[0], &element_type))
+      return false;
+    out_params->element_type_resolved = element_type;
+    out_params->element_type_raw = unresolved_args.parameters->items[0]->AsTypeCtor();
+
+    VectorType type(name_, element_type);
+    return type.ApplyConstraints(lib, *unresolved_args.constraints, this, out_type, out_params);
+  }
+
+  bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args,
+              std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
+    std::unique_ptr<CreateInvocation> args;
+    if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params))
+      return false;
+
+    assert(!args->handle_subtype);
+    assert(!args->handle_rights);
+
+    if (args->arg_type == nullptr)
+      return Fail(ErrMustBeParameterized, args->name.span());
+    VectorType type(name_, args->arg_type);
+    return type.ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type, out_params);
+  }
+};
+
+bool VectorType::ApplyConstraints(const flat::LibraryMediator& lib,
+                                  const TypeConstraints& constraints, const TypeTemplate* layout,
+                                  std::unique_ptr<Type>* out_type,
+                                  LayoutInvocation* out_params) const {
+  if (!ResolveSizeAndNullability(lib, constraints, layout, out_params))
+    return false;
+
+  bool is_already_nullable = nullability == types::Nullability::kNullable;
+  bool is_nullability_applied = out_params->nullability == types::Nullability::kNullable;
+  if (is_already_nullable && is_nullability_applied)
+    return lib.Fail(ErrCannotIndicateNullabilityTwice, std::nullopt, layout);
+  auto merged_nullability = is_already_nullable || is_nullability_applied
+                                ? types::Nullability::kNullable
+                                : types::Nullability::kNonnullable;
+
+  if (element_count != &kMaxSize && out_params->size_resolved)
+    return lib.Fail(ErrCannotBoundTwice, std::nullopt, layout);
+  auto merged_size = out_params->size_resolved ? out_params->size_resolved : element_count;
+
+  *out_type = std::make_unique<VectorType>(name, element_type, merged_size, merged_nullability);
+  return true;
+}
+
+bool VectorType::ApplySomeLayoutParametersAndConstraints(const LibraryMediator& lib,
+                                                         const CreateInvocation& create_invocation,
+                                                         const TypeTemplate* layout,
+                                                         std::unique_ptr<Type>* out_type,
+                                                         LayoutInvocation* out_params) const {
+  bool is_already_nullable = nullability == types::Nullability::kNullable;
+  bool is_nullability_applied = create_invocation.nullability == types::Nullability::kNullable;
+  if (is_already_nullable && is_nullability_applied)
+    return lib.Fail(ErrCannotIndicateNullabilityTwice, create_invocation.name.span(), layout);
+  auto merged_nullability = is_already_nullable || is_nullability_applied
+                                ? types::Nullability::kNullable
+                                : types::Nullability::kNonnullable;
+
+  // TODO(fxbug.dev/74193): take the smaller bound
+  if (element_count != &kMaxSize && create_invocation.size) {
+    return lib.Fail(ErrCannotBoundTwice, std::nullopt, layout);
+  }
+  auto merged_size = create_invocation.size ? create_invocation.size : element_count;
+
+  *out_type = std::make_unique<VectorType>(name, element_type, merged_size, merged_nullability);
+  return true;
+}
+
+class StringTypeTemplate final : public TypeTemplate {
+ public:
+  StringTypeTemplate(Typespace* typespace, Reporter* reporter)
+      : TypeTemplate(Name::CreateIntrinsic("string"), typespace, reporter) {}
+
+  bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args,
+              std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
+    size_t num_params = unresolved_args.parameters->items.size();
+    if (num_params != 0) {
+      return Fail(ErrWrongNumberOfLayoutParameters, unresolved_args.parameters->span, size_t(0),
+                  num_params);
+    }
+
+    StringType type(name_);
+    return type.ApplyConstraints(lib, *unresolved_args.constraints, this, out_type, out_params);
+  }
+
+  bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args,
+              std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
+    std::unique_ptr<CreateInvocation> args;
+    if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params))
+      return false;
+
+    assert(!args->handle_subtype);
+    assert(!args->handle_rights);
+
+    if (args->arg_type != nullptr)
+      return Fail(ErrCannotBeParameterized, args->name.span());
+
+    StringType type(name_);
+    return type.ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type, out_params);
+  }
+};
+
+bool StringType::ApplyConstraints(const flat::LibraryMediator& lib,
+                                  const TypeConstraints& constraints, const TypeTemplate* layout,
+                                  std::unique_ptr<Type>* out_type,
+                                  LayoutInvocation* out_params) const {
+  if (!ResolveSizeAndNullability(lib, constraints, layout, out_params))
+    return false;
+
+  bool is_already_nullable = nullability == types::Nullability::kNullable;
+  bool is_nullability_applied = out_params->nullability == types::Nullability::kNullable;
+  if (is_already_nullable && is_nullability_applied)
+    return lib.Fail(ErrCannotIndicateNullabilityTwice, std::nullopt, layout);
+  auto merged_nullability = is_already_nullable || is_nullability_applied
+                                ? types::Nullability::kNullable
+                                : types::Nullability::kNonnullable;
+
+  if (max_size != &kMaxSize && out_params->size_resolved)
+    return lib.Fail(ErrCannotBoundTwice, std::nullopt, layout);
+  auto merged_size = out_params->size_resolved ? out_params->size_resolved : max_size;
+
+  *out_type = std::make_unique<StringType>(name, merged_size, merged_nullability);
+  return true;
+}
+
+bool StringType::ApplySomeLayoutParametersAndConstraints(const LibraryMediator& lib,
+                                                         const CreateInvocation& create_invocation,
+                                                         const TypeTemplate* layout,
+                                                         std::unique_ptr<Type>* out_type,
+                                                         LayoutInvocation* out_params) const {
+  bool is_already_nullable = nullability == types::Nullability::kNullable;
+  bool is_nullability_applied = create_invocation.nullability == types::Nullability::kNullable;
+  if (is_already_nullable && is_nullability_applied)
+    return lib.Fail(ErrCannotIndicateNullabilityTwice, create_invocation.name.span(), layout);
+  auto merged_nullability = is_already_nullable || is_nullability_applied
+                                ? types::Nullability::kNullable
+                                : types::Nullability::kNonnullable;
+
+  // Note that we don't have a way of knowing whether a size was actually specified,
+  // since unspecified sizes are always replaced with a MAX default. Assume that
+  // MAX means unspecified (this means that we would allow bounding twice if the
+  // user uses MAX both times).
+  // TODO(fxbug.dev/74193): take the smaller bound
+  if (*max_size != kMaxSize && create_invocation.size)
+    return lib.Fail(ErrCannotBoundTwice, std::nullopt, layout);
+  auto merged_size = create_invocation.size ? create_invocation.size : max_size;
+
+  *out_type = std::make_unique<StringType>(name, merged_size, merged_nullability);
+  return true;
+}
 
 class HandleTypeTemplate final : public TypeTemplate {
  public:
   HandleTypeTemplate(Typespace* typespace, Reporter* reporter)
       : TypeTemplate(Name::CreateIntrinsic("handle"), typespace, reporter) {}
 
-  bool Create(const LibraryMediator& lib, const ArgsAndConstraintsOld& unresolved_args,
-              std::unique_ptr<Type>* out_type,
-              std::optional<TypeConstructorOld::FromTypeAlias>* out_from_type_alias) const {
-    std::unique_ptr<CreateInvocation> args;
-    if (!ResolveArgs(lib, unresolved_args, &args))
-      return false;
-
-    assert(args->arg_type == nullptr);
-
-    if (args->size != nullptr)
-      return Fail(ErrCannotHaveSize, args->name.span());
-
-    Resource* handle_resource_decl = nullptr;
-    if (!GetResource(lib, unresolved_args.name, &handle_resource_decl))
-      return false;
-
-    // TODO(fxbug.dev/64629): When we are ready to create handle types, we
-    // should have an object type (and/or subtype) determined and not require
-    // these hardcoded defaults.
-    // We need to allow setting a default obj_type in resource_definition
-    // declarations rather than hard-coding.
-    auto obj_type = args->obj_type.value_or(static_cast<uint32_t>(types::HandleSubtype::kHandle));
-    auto handle_subtype = args->handle_subtype.value_or(types::HandleSubtype::kHandle);
-    const HandleRights* handle_rights = args->handle_rights;
-    if (handle_rights == nullptr)
-      handle_rights = &kSameRights;
-
-    *out_type = std::make_unique<HandleType>(name_, handle_resource_decl, obj_type, handle_subtype,
-                                             handle_rights, args->nullability);
-    return true;
-  }
-
   // Currently we take a name as parameter, but the parser restricts this name to be
   // something that ends in "handle".
   // In a more general implementation, we would add such an entry at "Consume" time of
   // the resource in question, allowing us to set a pointer to the Resource declaration
-  // in on the HandleTypeTemplate itself. We can't currently do this because we don't have
+  // on the HandleTypeTemplate itself. We can't currently do this because we don't have
   // access to the definition of "handle" when we insert it into the root typespace, so we
   // need to resort to looking it up and doing validation at runtime.
-  bool GetResource(const LibraryMediator& lib, const Name& name, Resource** out_resource) const {
+  bool GetResource(const LibraryMediator& lib, const Name& name,
+                   Resource** out_resource) const override {
     Decl* handle_decl = lib.LookupDeclByName(name);
     if (!handle_decl || handle_decl->kind != Decl::Kind::kResource) {
       // TODO(fxbug.dev/74909): We can't error yet, because this may be a bare
@@ -659,7 +917,8 @@
     }
 
     auto* resource = static_cast<Resource*>(handle_decl);
-    if (!resource->subtype_ctor || resource->subtype_ctor->name.full_name() != "uint32") {
+    if (!IsTypeConstructorDefined(resource->subtype_ctor) ||
+        GetName(resource->subtype_ctor).full_name() != "uint32") {
       reporter_->Report(ErrResourceMustBeUint32Derived, resource->name);
       return false;
     }
@@ -668,22 +927,231 @@
     return true;
   }
 
+  bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& args,
+              std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
+    size_t num_params = !args.parameters->items.empty();
+    if (num_params)
+      return Fail(ErrWrongNumberOfLayoutParameters, args.parameters->span, size_t(0), num_params);
+
+    Resource* handle_resource_decl = nullptr;
+    if (!GetResource(lib, args.name, &handle_resource_decl))
+      return false;
+
+    HandleType type(name_, handle_resource_decl);
+    return type.ApplyConstraints(lib, *args.constraints, this, out_type, out_params);
+  }
+
+  bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& args,
+              std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
+    std::unique_ptr<CreateInvocation> resolved;
+    if (!ResolveOldSyntaxArgs(lib, args, &resolved, out_params))
+      return false;
+
+    assert(resolved->arg_type == nullptr);
+
+    if (resolved->size != nullptr)
+      return Fail(ErrCannotHaveSize, resolved->name.span());
+
+    // Note that in the old syntax, we'll already have looked up the Resource*
+    // (if necessary) since the old syntax resolves arguments ahead of time (see
+    // call to ResolveOldSyntaxArgs above). However, we still need to obtain the
+    // Resource* and pass it to the HandleType, since it may be used to resolve
+    // more constraints later (e.g. if there's an alias to this handle that also
+    // specifies more constraints) in the new syntax.
+    Resource* handle_resource_decl = nullptr;
+    if (!GetResource(lib, args.name, &handle_resource_decl))
+      return false;
+
+    HandleType type(name_, handle_resource_decl);
+    return type.ApplySomeLayoutParametersAndConstraints(lib, *resolved, this, out_type, out_params);
+  }
+
  private:
   const static HandleRights kSameRights;
 };
 
-const HandleRights HandleTypeTemplate::kSameRights = HandleRights(kHandleSameRights);
+const HandleRights HandleType::kSameRights = HandleRights(kHandleSameRights);
+
+bool HandleType::ApplyConstraints(const flat::LibraryMediator& lib,
+                                  const TypeConstraints& constraints, const TypeTemplate* layout,
+                                  std::unique_ptr<Type>* out_type,
+                                  LayoutInvocation* out_params) const {
+  // We need to store this separately from out_params, because out_params doesn't
+  // store the raw Constant that gets resolved to a nullability constraint.
+  std::optional<SourceSpan> applied_nullability_span;
+
+  size_t num_constraints = constraints.items.size();
+
+  // There's a bit of a chicken and egg problem here: we only want to error when the
+  // resource_decl is undefined while trying to resolve a subtype or handle rights
+  // constant, but we don't know if a constraint is a subtype or handle rights or
+  // something else (like optional) without the resource_decl.
+  // In practice, such an edge case is almost guaranteed not to happen, so just error
+  // if there is no resource_decl and there are any constraints at all.
+  if (num_constraints > 0 && !resource_decl) {
+    // TODO(fxbug.dev/74909): Once bare handles are disallowed we won't need to
+    // throw this error "at use time" anymore and can move it back into
+    // HandleTypeTemplate::GetResource. At that point, we can also add back some
+    // more helpful data to the error (like the offending type constructor's name
+    // and span) which we don't have access to here.
+    return lib.Fail(ErrHandleSubtypeNotResource, std::nullopt);
+  }
+
+  if (num_constraints == 0) {
+    // no constraints: set to default subtype below
+  } else if (num_constraints == 1) {
+    // lone constraint can be either subtype or optional
+    auto constraint_span = constraints.items[0]->span;
+    LibraryMediator::ResolvedConstraint resolved;
+    if (!lib.ResolveConstraintAs(constraints.items[0],
+                                 {LibraryMediator::ConstraintKind::kHandleSubtype,
+                                  LibraryMediator::ConstraintKind::kNullability},
+                                 resource_decl, &resolved))
+      return lib.Fail(ErrUnexpectedConstraint, constraint_span, layout);
+    switch (resolved.kind) {
+      case LibraryMediator::ConstraintKind::kHandleSubtype:
+        out_params->subtype_resolved = resolved.value.handle_subtype;
+        out_params->subtype_raw = constraints.items[0].get();
+        break;
+      case LibraryMediator::ConstraintKind::kNullability:
+        out_params->nullability = types::Nullability::kNullable;
+        applied_nullability_span = constraint_span;
+        break;
+      default:
+        assert(false && "Compiler bug: resolved to wrong constraint kind");
+    }
+  } else if (num_constraints == 2) {
+    // the first constraint must be subtype
+    auto constraint_span = constraints.items[0]->span;
+    uint32_t obj_type = 0;
+    if (!lib.ResolveAsHandleSubtype(resource_decl, constraints.items[0], &obj_type))
+      return lib.Fail(ErrUnexpectedConstraint, constraint_span, layout);
+    out_params->subtype_resolved = obj_type;
+    out_params->subtype_raw = constraints.items[0].get();
+
+    // the second constraint can either be rights or optional
+    constraint_span = constraints.items[1]->span;
+    LibraryMediator::ResolvedConstraint resolved;
+    if (!lib.ResolveConstraintAs(constraints.items[1],
+                                 {LibraryMediator::ConstraintKind::kHandleRights,
+                                  LibraryMediator::ConstraintKind::kNullability},
+                                 resource_decl, &resolved))
+      return lib.Fail(ErrUnexpectedConstraint, constraint_span, layout);
+    switch (resolved.kind) {
+      case LibraryMediator::ConstraintKind::kHandleRights:
+        out_params->rights_resolved = resolved.value.handle_rights;
+        out_params->rights_raw = constraints.items[1].get();
+        break;
+      case LibraryMediator::ConstraintKind::kNullability:
+        out_params->nullability = types::Nullability::kNullable;
+        applied_nullability_span = constraint_span;
+        break;
+      default:
+        assert(false && "Compiler bug: resolved to wrong constraint kind");
+    }
+  } else if (num_constraints == 3) {
+    // no degrees of freedom: must be subtype, followed by rights, then optional
+    uint32_t obj_type = 0;
+    if (!lib.ResolveAsHandleSubtype(resource_decl, constraints.items[0], &obj_type))
+      return lib.Fail(ErrUnexpectedConstraint, constraints.items[0]->span, layout);
+    out_params->subtype_resolved = obj_type;
+    out_params->subtype_raw = constraints.items[0].get();
+    const HandleRights* rights = nullptr;
+    if (!lib.ResolveAsHandleRights(resource_decl, constraints.items[1].get(), &rights))
+      return lib.Fail(ErrUnexpectedConstraint, constraints.items[1]->span, layout);
+    out_params->rights_resolved = rights;
+    out_params->rights_raw = constraints.items[1].get();
+    if (!lib.ResolveAsOptional(constraints.items[2].get()))
+      return lib.Fail(ErrUnexpectedConstraint, constraints.items[2]->span, layout);
+    out_params->nullability = types::Nullability::kNullable;
+    applied_nullability_span = constraints.items[2]->span;
+  } else {
+    return lib.Fail(ErrTooManyConstraints, constraints.span, layout, size_t(3), num_constraints);
+  }
+
+  bool has_obj_type = subtype != types::HandleSubtype::kHandle;
+  if (has_obj_type && out_params->subtype_resolved)
+    return lib.Fail(ErrCannotConstrainTwice, out_params->subtype_raw->span, layout);
+  // TODO(fxbug.dev/64629): We need to allow setting a default obj_type in
+  // resource_definition declarations rather than hard-coding.
+  uint32_t merged_obj_type = obj_type;
+  if (out_params->subtype_resolved) {
+    merged_obj_type = out_params->subtype_resolved.value();
+  }
+
+  bool has_nullability = nullability == types::Nullability::kNullable;
+  if (has_nullability && out_params->nullability == types::Nullability::kNullable)
+    return lib.Fail(ErrCannotIndicateNullabilityTwice, applied_nullability_span, layout);
+  auto merged_nullability =
+      has_nullability || out_params->nullability == types::Nullability::kNullable
+          ? types::Nullability::kNullable
+          : types::Nullability::kNonnullable;
+
+  bool has_rights = rights != &kSameRights;
+  if (has_rights && out_params->rights_resolved)
+    return lib.Fail(ErrCannotConstrainTwice, out_params->rights_raw->span, layout);
+  auto merged_rights = rights;
+  if (out_params->rights_resolved) {
+    merged_rights = out_params->rights_resolved;
+  }
+
+  *out_type = std::make_unique<HandleType>(name, resource_decl, merged_obj_type,
+                                           types::HandleSubtype(merged_obj_type), merged_rights,
+                                           merged_nullability);
+  return true;
+}
+
+bool HandleType::ApplySomeLayoutParametersAndConstraints(const LibraryMediator& lib,
+                                                         const CreateInvocation& create_invocation,
+                                                         const TypeTemplate* layout,
+                                                         std::unique_ptr<Type>* out_type,
+                                                         LayoutInvocation* out_params) const {
+  if (create_invocation.size)
+    return lib.Fail(ErrCannotHaveSize, create_invocation.name.span(), layout);
+
+  bool has_obj_type = subtype != types::HandleSubtype::kHandle;
+  if (has_obj_type && create_invocation.obj_type)
+    return lib.Fail(ErrCannotConstrainTwice, std::nullopt, layout);
+  uint32_t merged_obj_type = obj_type;
+  if (create_invocation.obj_type.has_value())
+    merged_obj_type = create_invocation.obj_type.value();
+
+  bool has_nullability = nullability == types::Nullability::kNullable;
+  if (has_nullability && create_invocation.nullability == types::Nullability::kNullable)
+    return lib.Fail(ErrCannotIndicateNullabilityTwice, std::nullopt, layout);
+  auto merged_nullability =
+      has_nullability || create_invocation.nullability == types::Nullability::kNullable
+          ? types::Nullability::kNullable
+          : types::Nullability::kNonnullable;
+
+  bool has_rights = rights != &kSameRights;
+  if (has_rights && create_invocation.handle_rights)
+    return lib.Fail(ErrCannotConstrainTwice, std::nullopt, layout);
+  auto merged_rights = rights;
+  if (create_invocation.handle_rights)
+    merged_rights = create_invocation.handle_rights;
+
+  *out_type = std::make_unique<HandleType>(name, resource_decl, merged_obj_type,
+                                           types::HandleSubtype(merged_obj_type), merged_rights,
+                                           merged_nullability);
+  return true;
+}
 
 class RequestTypeTemplate final : public TypeTemplate {
  public:
   RequestTypeTemplate(Typespace* typespace, Reporter* reporter)
       : TypeTemplate(Name::CreateIntrinsic("request"), typespace, reporter) {}
 
-  bool Create(const LibraryMediator& lib, const ArgsAndConstraintsOld& unresolved_args,
-              std::unique_ptr<Type>* out_type,
-              std::optional<TypeConstructorOld::FromTypeAlias>* out_from_type_alias) const {
+  bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args,
+              std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
+    assert(false && "Compiler bug: this type template should only be used in the old syntax");
+    return false;
+  }
+
+  bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args,
+              std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
     std::unique_ptr<CreateInvocation> args;
-    if (!ResolveArgs(lib, unresolved_args, &args))
+    if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params))
       return false;
 
     assert(!args->handle_subtype);
@@ -699,11 +1167,38 @@
     if (args->size != nullptr)
       return Fail(ErrCannotHaveSize, args->name.span());
 
-    *out_type = std::make_unique<RequestHandleType>(name_, protocol_type, args->nullability);
-    return true;
+    RequestHandleType type(name_, protocol_type);
+    return type.ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type, out_params);
   }
 };
 
+bool RequestHandleType::ApplyConstraints(const flat::LibraryMediator& lib,
+                                         const TypeConstraints& constraints,
+                                         const TypeTemplate* layout,
+                                         std::unique_ptr<Type>* out_type,
+                                         LayoutInvocation* out_params) const {
+  assert(false && "Compiler bug: this type should only be used in the old syntax");
+  return false;
+}
+
+bool RequestHandleType::ApplySomeLayoutParametersAndConstraints(
+    const LibraryMediator& lib, const CreateInvocation& create_invocation,
+    const TypeTemplate* layout, std::unique_ptr<Type>* out_type,
+    LayoutInvocation* out_params) const {
+  if (create_invocation.size)
+    return lib.Fail(ErrCannotHaveSize, create_invocation.name.span(), layout);
+
+  if (nullability == types::Nullability::kNullable &&
+      create_invocation.nullability == types::Nullability::kNullable)
+    return lib.Fail(ErrCannotIndicateNullabilityTwice, std::nullopt, layout);
+  auto merged_nullability = nullability;
+  if (create_invocation.nullability == types::Nullability::kNullable)
+    merged_nullability = create_invocation.nullability;
+
+  *out_type = std::make_unique<RequestHandleType>(name, protocol_type, merged_nullability);
+  return true;
+}
+
 class TypeDeclTypeTemplate final : public TypeTemplate {
  public:
   TypeDeclTypeTemplate(Name name, Typespace* typespace, Reporter* reporter, Library* library,
@@ -712,11 +1207,32 @@
         library_(library),
         type_decl_(type_decl) {}
 
-  bool Create(const LibraryMediator& lib, const ArgsAndConstraintsOld& unresolved_args,
-              std::unique_ptr<Type>* out_type,
-              std::optional<TypeConstructorOld::FromTypeAlias>* out_from_type_alias) const {
+  bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args,
+              std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
+    if (!type_decl_->compiled && type_decl_->kind != Decl::Kind::kProtocol) {
+      if (type_decl_->compiling) {
+        type_decl_->recursive = true;
+      } else {
+        if (!library_->CompileDecl(type_decl_)) {
+          return false;
+        }
+      }
+    }
+
+    size_t num_params = unresolved_args.parameters->items.size();
+    if (num_params != 0) {
+      return Fail(ErrWrongNumberOfLayoutParameters, unresolved_args.parameters->span, size_t(0),
+                  num_params);
+    }
+
+    IdentifierType type(name_, type_decl_);
+    return type.ApplyConstraints(lib, *unresolved_args.constraints, this, out_type, out_params);
+  }
+
+  bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args,
+              std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
     std::unique_ptr<CreateInvocation> args;
-    if (!ResolveArgs(lib, unresolved_args, &args))
+    if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params))
       return false;
 
     assert(!args->handle_subtype);
@@ -730,39 +1246,12 @@
         }
       }
     }
-    switch (type_decl_->kind) {
-      case Decl::Kind::kService:
-      case Decl::Kind::kProtocol:
-        break;
 
-      case Decl::Kind::kUnion:
-        // Do nothing here: nullable Unions have the same encoding
-        // representation as non-optional Unions (i.e. nullable Unions are
-        // inlined).
-        break;
+    if (args->arg_type != nullptr)
+      return Fail(ErrCannotBeParameterized, args->name.span());
 
-      case Decl::Kind::kBits:
-      case Decl::Kind::kEnum:
-      case Decl::Kind::kTable:
-        if (args->nullability == types::Nullability::kNullable)
-          return Fail(ErrCannotBeNullable, args->name.span());
-        break;
-
-      case Decl::Kind::kResource: {
-        // Currently the only resource types are new-style handles,
-        // and they should be resolved to concrete subtypes and
-        // dispatched to the handle template earlier.
-        assert(false);
-        break;
-      }
-
-      default:
-        if (args->nullability == types::Nullability::kNullable)
-          break;
-    }
-
-    *out_type = std::make_unique<IdentifierType>(name_, args->nullability, type_decl_);
-    return true;
+    IdentifierType type(name_, type_decl_);
+    return type.ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type, out_params);
   }
 
  private:
@@ -770,16 +1259,159 @@
   TypeDecl* type_decl_;
 };
 
+bool IdentifierType::ApplyConstraints(const flat::LibraryMediator& lib,
+                                      const TypeConstraints& constraints,
+                                      const TypeTemplate* layout, std::unique_ptr<Type>* out_type,
+                                      LayoutInvocation* out_params) const {
+  size_t num_constraints = constraints.items.size();
+  switch (type_decl->kind) {
+    // These types have no allowed constraints
+    case Decl::Kind::kBits:
+    case Decl::Kind::kEnum:
+    case Decl::Kind::kTable:
+      // assume that a lone constraint was an attempt at specifying `optional` and provide a more
+      // specific error
+      // TOOD(fxbug.dev/75112): actually try to compile the optional constraint
+      if (num_constraints == 1)
+        return lib.Fail(ErrCannotBeNullable, constraints.items[0]->span, layout);
+      if (num_constraints > 1) {
+        return lib.Fail(ErrTooManyConstraints, constraints.span, layout, size_t(0),
+                        num_constraints);
+      }
+      break;
+
+    // These types have one allowed constraint (`optional`). For type aliases,
+    // we need to allow the possibility that the concrete type does allow `optional`,
+    // if it doesn't the Type itself will catch the error.
+    case Decl::Kind::kTypeAlias:
+    case Decl::Kind::kStruct:
+    case Decl::Kind::kUnion:
+      if (num_constraints > 1) {
+        return lib.Fail(ErrTooManyConstraints, constraints.span, layout, size_t(1),
+                        num_constraints);
+      }
+      break;
+
+    case Decl::Kind::kConst:
+    case Decl::Kind::kResource:
+      // Cannot have const: entries for constants do not exist in the typespace, so
+      // they're caught earlier.
+      // Cannot have resource: resource types should have resolved to the HandleTypeTemplate
+      assert(false && "Compiler bug: unexpected identifier type decl kind");
+      break;
+
+    // TODO(fxbug.dev/75837):
+    // These can't be used as types. This will be caught later, in VerifyTypeCategory.
+    case Decl::Kind::kService:
+    case Decl::Kind::kProtocol:
+      break;
+  }
+
+  types::Nullability applied_nullability = types::Nullability::kNonnullable;
+  if (num_constraints == 1) {
+    // must be optional
+    if (!lib.ResolveAsOptional(constraints.items[0].get()))
+      return lib.Fail(ErrUnexpectedConstraint, constraints.items[0]->span, layout);
+    applied_nullability = types::Nullability::kNullable;
+  }
+
+  if (nullability == types::Nullability::kNullable &&
+      applied_nullability == types::Nullability::kNullable)
+    return lib.Fail(ErrCannotIndicateNullabilityTwice, std::nullopt, layout);
+  auto merged_nullability = nullability;
+  if (applied_nullability == types::Nullability::kNullable)
+    merged_nullability = applied_nullability;
+
+  out_params->nullability = applied_nullability;
+  *out_type = std::make_unique<IdentifierType>(name, type_decl, merged_nullability);
+  return true;
+}
+
+bool IdentifierType::ApplySomeLayoutParametersAndConstraints(
+    const LibraryMediator& lib, const CreateInvocation& create_invocation,
+    const TypeTemplate* layout, std::unique_ptr<Type>* out_type,
+    LayoutInvocation* out_params) const {
+  switch (type_decl->kind) {
+    // These types can't be nullable
+    case Decl::Kind::kBits:
+    case Decl::Kind::kEnum:
+    case Decl::Kind::kTable:
+      if (create_invocation.nullability == types::Nullability::kNullable)
+        return lib.Fail(ErrCannotBeNullable, create_invocation.name.span(), layout);
+      break;
+
+    // These types have one allowed constraint (`optional`). For type aliases,
+    // we need to allow the possibility that the concrete type does allow `optional`,
+    // if it doesn't the Type itself will catch the error.
+    case Decl::Kind::kProtocol:
+    case Decl::Kind::kTypeAlias:
+    case Decl::Kind::kStruct:
+    case Decl::Kind::kUnion:
+      if (nullability == types::Nullability::kNullable &&
+          create_invocation.nullability == types::Nullability::kNullable)
+        return lib.Fail(ErrCannotIndicateNullabilityTwice, std::nullopt, layout);
+      break;
+
+    // These should never be encountered
+    case Decl::Kind::kConst:
+    case Decl::Kind::kResource: {
+      // Cannot have const: entries for constants do not exist in the typespace
+      // Cannot have resource: resource types should have resolved to the HandleTypeTemplate
+      assert(false);
+      break;
+    }
+
+    // TODO(fxbug.dev/75837):
+    // Services are not allowed to be used as types. This is caught later, during
+    // VerifyTypeCategory.
+    case Decl::Kind::kService:
+      break;
+  }
+
+  auto merged_nullability = nullability;
+  if (create_invocation.nullability == types::Nullability::kNullable)
+    merged_nullability = create_invocation.nullability;
+
+  *out_type = std::make_unique<IdentifierType>(name, type_decl, merged_nullability);
+  return true;
+}
+
 class TypeAliasTypeTemplate final : public TypeTemplate {
  public:
   TypeAliasTypeTemplate(Name name, Typespace* typespace, Reporter* reporter, TypeAlias* decl)
       : TypeTemplate(std::move(name), typespace, reporter), decl_(decl) {}
 
-  bool Create(const LibraryMediator& lib, const ArgsAndConstraintsOld& unresolved_args,
-              std::unique_ptr<Type>* out_type,
-              std::optional<TypeConstructorOld::FromTypeAlias>* out_from_type_alias) const {
+  bool Create(const LibraryMediator& lib, const NewSyntaxParamsAndConstraints& unresolved_args,
+              std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
+    if (!decl_->compiled) {
+      if (decl_->compiling) {
+        return Fail(ErrIncludeCycle);
+      }
+
+      if (!lib.CompileDecl(decl_)) {
+        return false;
+      }
+    }
+
+    size_t num_params = unresolved_args.parameters->items.size();
+    if (num_params != 0)
+      return Fail(ErrWrongNumberOfLayoutParameters, unresolved_args.parameters->span, size_t(0),
+                  num_params);
+
+    // Compilation failed while trying to resolve something farther up the chain;
+    // exit early
+    if (!GetType(decl_->partial_type_ctor))
+      return false;
+    const auto& aliased_type = GetType(decl_->partial_type_ctor);
+    out_params->from_type_alias = decl_;
+    return aliased_type->ApplyConstraints(lib, *unresolved_args.constraints, this, out_type,
+                                          out_params);
+  }
+
+  bool Create(const LibraryMediator& lib, const OldSyntaxParamsAndConstraints& unresolved_args,
+              std::unique_ptr<Type>* out_type, LayoutInvocation* out_params) const override {
     std::unique_ptr<CreateInvocation> args;
-    if (!ResolveArgs(lib, unresolved_args, &args))
+    if (!ResolveOldSyntaxArgs(lib, unresolved_args, &args, out_params))
       return false;
 
     // Note that because fidlc only populates these handle fields if it sees
@@ -799,48 +1431,17 @@
       }
     }
 
-    const auto& aliased_type = decl_->partial_type_ctor;
-
-    if (unresolved_args.maybe_arg_type_ctor)
+    if (unresolved_args.maybe_arg_type_ctor != nullptr)
       return Fail(ErrCannotParameterizeAlias, args->name.span());
-    // We know unresolved_args.maybe_arg_type_ctor is null, but need to use a conditional here
-    // for the compiler to accept the reference
-    const auto& maybe_arg_type_ctor = aliased_type->maybe_arg_type_ctor;
 
-    // TODO(fxbug.dev/74193): This code needs to "merge" the two size constraints
-    // by taking the stronger of the two (smaller bound). If we are applying a weaker
-    // constraint (i.e. the arg has no effect), we could consider making this a warning.
-    if (unresolved_args.maybe_size && aliased_type->maybe_size)
-      return Fail(ErrCannotBoundTwice, unresolved_args.name.span());
-    const auto& maybe_size =
-        unresolved_args.maybe_size ? unresolved_args.maybe_size : aliased_type->maybe_size;
-
-    // TODO(fxbug.dev/74193): Restrict rights further rather than just taking the
-    // rights specified in the aliased type.
-    // We know unresolved_args.handle_subtype_identifier is null, but need to use a conditional here
-    // for the compiler to accept the reference (see asserts above)
-    const auto& handle_subtype_identifier = unresolved_args.handle_subtype_identifier
-                                                ? unresolved_args.handle_subtype_identifier
-                                                : aliased_type->handle_subtype_identifier;
-
-    // TODO(fxbug.dev/74193): The type is nullable if optional is specified in
-    // either context
-    bool is_arg_nullable = unresolved_args.nullability == types::Nullability::kNullable;
-    bool is_aliased_nullable = aliased_type->nullability == types::Nullability::kNullable;
-    if (is_arg_nullable && is_aliased_nullable)
-      return Fail(ErrCannotIndicateNullabilityTwice, unresolved_args.name.span());
-    types::Nullability nullability = is_arg_nullable || is_aliased_nullable
-                                         ? types::Nullability::kNullable
-                                         : types::Nullability::kNonnullable;
-
-    if (!typespace_->CreateNotOwned(
-            lib, aliased_type->name, maybe_arg_type_ctor, handle_subtype_identifier,
-            decl_->partial_type_ctor->handle_rights, maybe_size, nullability, out_type, nullptr))
+    // Compilation failed while trying to resolve something farther up the chain;
+    // exit early
+    if (!GetType(decl_->partial_type_ctor))
       return false;
-    if (out_from_type_alias)
-      *out_from_type_alias = TypeConstructorOld::FromTypeAlias(
-          decl_, args->arg_type, args->size, args->handle_subtype, args->nullability);
-    return true;
+    const auto& aliased_type = GetType(decl_->partial_type_ctor);
+    out_params->from_type_alias = decl_;
+    return aliased_type->ApplySomeLayoutParametersAndConstraints(lib, *args, this, out_type,
+                                                                 out_params);
   }
 
  private:
@@ -946,7 +1547,7 @@
   auto struct_decl = static_cast<const Struct*>(decl);
   bool ok = true;
   for (const auto& member : struct_decl->members) {
-    if (!IsSimple(member.type_ctor.get()->type, reporter)) {
+    if (!IsSimple(GetType(member.type_ctor), reporter)) {
       reporter->Report(ErrMemberMustBeSimple, member.name, member.name.data());
       ok = false;
     }
@@ -1050,7 +1651,7 @@
   assert(union_decl->members.size() == 2);
   auto& error_member = union_decl->members.at(1);
   assert(error_member.maybe_used && "must have an error member");
-  auto error_type = error_member.maybe_used->type_ctor->type;
+  auto error_type = GetType(error_member.maybe_used->type_ctor);
 
   const PrimitiveType* error_primitive = nullptr;
   if (error_type->kind == Type::Kind::kPrimitive) {
@@ -1059,8 +1660,8 @@
     auto identifier_type = static_cast<const IdentifierType*>(error_type);
     if (identifier_type->type_decl->kind == Decl::Kind::kEnum) {
       auto error_enum = static_cast<const Enum*>(identifier_type->type_decl);
-      assert(error_enum->subtype_ctor->type->kind == Type::Kind::kPrimitive);
-      error_primitive = static_cast<const PrimitiveType*>(error_enum->subtype_ctor->type);
+      assert(GetType(error_enum->subtype_ctor)->kind == Type::Kind::kPrimitive);
+      error_primitive = static_cast<const PrimitiveType*>(GetType(error_enum->subtype_ctor));
     }
   }
 
@@ -1114,8 +1715,8 @@
   return true;
 }
 
-const Resource::Property* Resource::LookupProperty(std::string_view name) {
-  for (const Property& property : properties) {
+Resource::Property* Resource::LookupProperty(std::string_view name) {
+  for (Property& property : properties) {
     if (property.name.data() == name.data()) {
       return &property;
     }
@@ -1612,7 +2213,9 @@
     }
     case raw::Constant::Kind::kLiteral: {
       auto literal = static_cast<raw::LiteralConstant*>(raw_constant.get());
-      *out_constant = std::make_unique<LiteralConstant>(std::move(literal->literal));
+      std::unique_ptr<LiteralConstant> out;
+      ConsumeLiteralConstant(literal, &out);
+      *out_constant = std::unique_ptr<Constant>(out.release());
       break;
     }
     case raw::Constant::Kind::kBinaryOperator: {
@@ -1639,6 +2242,11 @@
   return true;
 }
 
+void Library::ConsumeLiteralConstant(raw::LiteralConstant* raw_constant,
+                                     std::unique_ptr<LiteralConstant>* out_constant) {
+  *out_constant = std::make_unique<LiteralConstant>(std::move(raw_constant->literal));
+}
+
 bool Library::ConsumeTypeConstructorOld(std::unique_ptr<raw::TypeConstructorOld> raw_type_ctor,
                                         std::unique_ptr<TypeConstructorOld>* out_type_ctor) {
   auto name = CompileCompoundIdentifier(raw_type_ctor->identifier.get());
@@ -1672,13 +2280,11 @@
 
   *out_type_ctor = std::make_unique<TypeConstructorOld>(
       std::move(name.value()), std::move(maybe_arg_type_ctor), std::move(handle_subtype_identifier),
-      std::move(handle_rights), std::move(maybe_size), raw_type_ctor->nullability,
-      fidl::utils::Syntax::kOld);
+      std::move(handle_rights), std::move(maybe_size), raw_type_ctor->nullability);
   return true;
 }
 
-void Library::ConsumeUsing(std::unique_ptr<raw::Using> using_directive,
-                           fidl::utils::Syntax syntax) {
+void Library::ConsumeUsing(std::unique_ptr<raw::Using> using_directive) {
   if (using_directive->attributes && using_directive->attributes->attributes.size() != 0) {
     Fail(ErrAttributesNotAllowedOnLibraryImport, using_directive->span(),
          *(using_directive->attributes));
@@ -1708,12 +2314,11 @@
   declarations_.insert(declarations.begin(), declarations.end());
 }
 
-bool Library::ConsumeTypeAlias(std::unique_ptr<raw::AliasDeclaration> alias_declaration,
-                               fidl::utils::Syntax syntax) {
+bool Library::ConsumeTypeAlias(std::unique_ptr<raw::AliasDeclaration> alias_declaration) {
   assert(alias_declaration->alias && IsTypeConstructorDefined(alias_declaration->type_ctor));
 
   auto alias_name = Name::CreateSourced(this, alias_declaration->alias->span());
-  std::unique_ptr<TypeConstructorOld> type_ctor_;
+  TypeConstructor type_ctor_;
 
   if (!ConsumeTypeConstructor(std::move(alias_declaration->type_ctor), alias_name, &type_ctor_))
     return false;
@@ -1753,7 +2358,7 @@
   auto attributes = std::move(const_declaration->attributes);
   auto span = const_declaration->identifier->span();
   auto name = Name::CreateSourced(this, span);
-  std::unique_ptr<TypeConstructorOld> type_ctor;
+  TypeConstructor type_ctor;
   // TODO(fxbug.dev/73285): shouldn't need to care about context here
   if (!ConsumeTypeConstructor(std::move(const_declaration->type_ctor), name, &type_ctor))
     return;
@@ -1795,7 +2400,7 @@
 
 bool Library::CreateMethodResult(const Name& protocol_name, SourceSpan response_span,
                                  raw::ProtocolMethod* method, Struct* in_response,
-                                 fidl::utils::Syntax syntax, Struct** out_response) {
+                                 Struct** out_response) {
   // Compile the error type.
   std::unique_ptr<TypeConstructorOld> error_type_ctor;
   if (!ConsumeTypeConstructorOld(std::move(method->maybe_error_ctor), &error_type_ctor))
@@ -1812,8 +2417,9 @@
 
   raw::SourceElement sourceElement = raw::SourceElement(fidl::Token(), fidl::Token());
   Union::Member response_member{
-      std::make_unique<raw::Ordinal64>(sourceElement, 1),  // success case explicitly has ordinal 1
-      IdentifierTypeForDecl(in_response, types::Nullability::kNonnullable, syntax),
+      std::make_unique<raw::Ordinal64>(sourceElement,
+                                       1),  // success case explicitly has ordinal 1
+      IdentifierTypeForDecl(in_response, types::Nullability::kNonnullable),
       GeneratedSimpleName("response"), nullptr};
   Union::Member error_member{
       std::make_unique<raw::Ordinal64>(sourceElement, 2),  // error case explicitly has ordinal 2
@@ -1836,7 +2442,7 @@
   // result union.
   std::vector<Struct::Member> response_members;
   response_members.push_back(
-      Struct::Member(IdentifierTypeForDecl(result_decl, types::Nullability::kNonnullable, syntax),
+      Struct::Member(IdentifierTypeForDecl(result_decl, types::Nullability::kNonnullable),
                      GeneratedSimpleName("result"), nullptr, nullptr));
 
   auto struct_decl = std::make_unique<Struct>(
@@ -1851,7 +2457,7 @@
 }
 
 void Library::ConsumeProtocolDeclaration(
-    std::unique_ptr<raw::ProtocolDeclaration> protocol_declaration, fidl::utils::Syntax syntax) {
+    std::unique_ptr<raw::ProtocolDeclaration> protocol_declaration) {
   auto attributes = std::move(protocol_declaration->attributes);
   auto name = Name::CreateSourced(this, protocol_declaration->identifier->span());
 
@@ -1885,7 +2491,7 @@
       auto request_span = method->maybe_request->span();
       auto request_name = Name::CreateDerived(this, request_span, NextAnonymousName());
       if (!ConsumeParameterList(std::move(request_name), std::move(method->maybe_request), true,
-                                syntax, &maybe_request))
+                                &maybe_request))
         return;
     }
 
@@ -1899,12 +2505,11 @@
           has_error ? StringJoin({name.decl_name(), method_name.data(), "Response"}, "_")
                     : NextAnonymousName());
       if (!ConsumeParameterList(std::move(response_name), std::move(method->maybe_response),
-                                !has_error, syntax, &maybe_response))
+                                !has_error, &maybe_response))
         return;
 
       if (has_error) {
-        if (!CreateMethodResult(name, response_span, method.get(), maybe_response, syntax,
-                                &maybe_response))
+        if (!CreateMethodResult(name, response_span, method.get(), maybe_response, &maybe_response))
           return;
       }
     }
@@ -1920,11 +2525,11 @@
 }
 
 bool Library::ConsumeResourceDeclaration(
-    std::unique_ptr<raw::ResourceDeclaration> resource_declaration, fidl::utils::Syntax syntax) {
+    std::unique_ptr<raw::ResourceDeclaration> resource_declaration) {
   auto name = Name::CreateSourced(this, resource_declaration->identifier->span());
   std::vector<Resource::Property> properties;
   for (auto& property : resource_declaration->properties) {
-    std::unique_ptr<TypeConstructorOld> type_ctor;
+    TypeConstructor type_ctor;
     auto anonymous_resource_name = Name::CreateDerived(
         this, property->span(),
         std::string(name.decl_name()) + std::string(property->identifier->span().data()));
@@ -1936,7 +2541,7 @@
                             std::move(attributes));
   }
 
-  std::unique_ptr<TypeConstructorOld> type_ctor;
+  TypeConstructor type_ctor;
   if (raw::IsTypeConstructorDefined(resource_declaration->maybe_type_ctor)) {
     // TODO(fxbug.dev/73285): shouldn't need to care about the naming context here.
     if (!ConsumeTypeConstructor(std::move(resource_declaration->maybe_type_ctor), name, &type_ctor))
@@ -1951,20 +2556,18 @@
 }
 
 std::unique_ptr<TypeConstructorOld> Library::IdentifierTypeForDecl(const Decl* decl,
-                                                                   types::Nullability nullability,
-                                                                   fidl::utils::Syntax syntax) {
+                                                                   types::Nullability nullability) {
   return std::make_unique<TypeConstructorOld>(decl->name, nullptr /* maybe_arg_type */,
                                               std::optional<Name>() /* handle_subtype_identifier */,
                                               nullptr /* handle_rights */, nullptr /* maybe_size */,
-                                              nullability, syntax);
+                                              nullability);
 }
 
 bool Library::ConsumeParameterList(Name name, std::unique_ptr<raw::ParameterList> parameter_list,
-                                   bool is_request_or_response, fidl::utils::Syntax syntax,
-                                   Struct** out_struct_decl) {
+                                   bool is_request_or_response, Struct** out_struct_decl) {
   std::vector<Struct::Member> members;
   for (auto& parameter : parameter_list->parameter_list) {
-    std::unique_ptr<TypeConstructorOld> type_ctor;
+    TypeConstructor type_ctor;
     // TODO(fxbug.dev/73285): finalize layout naming
     auto param_name = Name::CreateDerived(
         this, parameter->span(),
@@ -1986,14 +2589,13 @@
   return true;
 }
 
-void Library::ConsumeServiceDeclaration(std::unique_ptr<raw::ServiceDeclaration> service_decl,
-                                        fidl::utils::Syntax syntax) {
+void Library::ConsumeServiceDeclaration(std::unique_ptr<raw::ServiceDeclaration> service_decl) {
   auto attributes = std::move(service_decl->attributes);
   auto name = Name::CreateSourced(this, service_decl->identifier->span());
 
   std::vector<Service::Member> members;
   for (auto& member : service_decl->members) {
-    std::unique_ptr<TypeConstructorOld> type_ctor;
+    TypeConstructor type_ctor;
     // TODO(fxbug.dev/73285): shouldn't need to care about naming context here.
     if (!ConsumeTypeConstructor(std::move(member->type_ctor), name, &type_ctor))
       return;
@@ -2117,12 +2719,12 @@
     index++;
   }
 
-  std::unique_ptr<TypeConstructorOld> subtype_ctor;
+  std::unique_ptr<TypeConstructorNew> subtype_ctor;
   if (layout->subtype_ctor != nullptr) {
     if (!ConsumeTypeConstructorNew(std::move(layout->subtype_ctor), context, &subtype_ctor))
       return false;
   } else {
-    subtype_ctor = TypeConstructorOld::CreateSizeType();
+    subtype_ctor = TypeConstructorNew::CreateSizeType();
   }
 
   RegisterDecl(std::make_unique<T>(
@@ -2145,17 +2747,10 @@
         this, member->span(),
         std::string(context.decl_name()) +
             utils::to_upper_camel_case(std::string(member->identifier->span().data())));
-    std::unique_ptr<TypeConstructorOld> type_ctor;
+    std::unique_ptr<TypeConstructorNew> type_ctor;
     if (!ConsumeTypeConstructorNew(std::move(member->type_ctor), name_of_anonymous_layout,
                                    &type_ctor))
       return false;
-    if (type_ctor->nullability != types::Nullability::kNonnullable) {
-      // TODO(fxbug.dev/65978): This error message could be more specific.  We
-      //  may want to just pass the specific error message as a template arg, or
-      //  otherwise handle this sort of validation when compiling.
-      Fail(ErrNullableOrdinaledMember, member->span());
-      return false;
-    }
 
     members.emplace_back(std::move(member->ordinal), std::move(type_ctor),
                          member->identifier->span(), /*attributes=*/nullptr);
@@ -2176,7 +2771,7 @@
         std::string(context.decl_name()) +
             utils::to_upper_camel_case(std::string(member->identifier->span().data())));
 
-    std::unique_ptr<TypeConstructorOld> type_ctor;
+    std::unique_ptr<TypeConstructorNew> type_ctor;
     if (!ConsumeTypeConstructorNew(std::move(member->type_ctor), name_of_anonymous_layout,
                                    &type_ctor))
       return false;
@@ -2217,231 +2812,118 @@
   return true;
 }
 
-bool Library::IsOptionalConstraint(std::unique_ptr<TypeConstructorOld>& type_ctor,
-                                   const std::unique_ptr<raw::Constant>& constant) {
-  if (constant->span().data() == "optional") {
-    return true;
-  }
-  return false;
-}
-
 bool Library::ConsumeTypeConstructorNew(std::unique_ptr<raw::TypeConstructorNew> raw_type_ctor,
                                         const Name& context,
-                                        std::unique_ptr<TypeConstructorOld>* out_type) {
-  auto params = std::move(raw_type_ctor->parameters);
-  auto constraints = std::move(raw_type_ctor->constraints);
-  size_t num_constraints = constraints == nullptr ? 0 : constraints->items.size();
-
+                                        std::unique_ptr<TypeConstructorNew>* out_type_ctor) {
   if (raw_type_ctor->layout_ref->kind == raw::LayoutReference::Kind::kInline) {
-    assert((params == nullptr || params->items.empty()) &&
-           "anonymous layouts cannot have type parameters");
+    // TODO(fxbug.dev/74683): If we don't want users to be able to refer to anonymous
+    // layouts by figuring out their generated name, we'll need to update this section
+    const auto& params = raw_type_ctor->parameters;
+    const auto& constraints = raw_type_ctor->constraints;
+    if (params && !params->items.empty()) {
+      return Fail(ErrLayoutCannotBeParameterized, params->span(), context);
+    }
+    if (constraints && !constraints->items.empty()) {
+      return Fail(ErrCannotConstrainInLayoutDecl, constraints->span());
+    }
     auto inline_ref = static_cast<raw::InlineLayoutReference*>(raw_type_ctor->layout_ref.get());
-    if (!ConsumeLayout(std::move(inline_ref->layout), context)) {
+    if (!ConsumeLayout(std::move(inline_ref->layout), context))
       return false;
-    }
-    auto type_ctor = std::make_unique<TypeConstructorOld>(
-        context,
-        /*maybe_arg_type_ctor=*/nullptr,
-        /*handle_subtype_identifier=*/std::nullopt,
-        /*handle_rights=*/nullptr,
-        /*maybe_size=*/nullptr, types::Nullability::kNonnullable, fidl::utils::Syntax::kNew);
 
-    // Inline layouts may only plausibly have one constraints ("optional"), so
-    // check it here.
-    if (num_constraints > 1) {
-      Fail(ErrConstraintsOverflow, type_ctor->name, size_t(1), num_constraints);
-    }
-    if (num_constraints == 1) {
-      if (IsOptionalConstraint(type_ctor, constraints->items[0])) {
-        type_ctor->nullability = types::Nullability::kNullable;
-      } else {
-        Fail(ErrConstraintOptionalMisspelled, type_ctor->name,
-             constraints->items[0]->span().data());
-        return false;
-      }
-    }
-    if (out_type)
-      *out_type = std::move(type_ctor);
+    std::vector<std::unique_ptr<LayoutParameter>> no_params;
+    std::vector<std::unique_ptr<Constant>> no_constraints;
+    if (out_type_ctor)
+      *out_type_ctor = std::make_unique<TypeConstructorNew>(
+          context, std::make_unique<LayoutParameterList>(std::move(no_params), std::nullopt),
+          std::make_unique<TypeConstraints>(std::move(no_constraints), std::nullopt));
     return true;
   }
 
   auto named_ref = static_cast<raw::NamedLayoutReference*>(raw_type_ctor->layout_ref.get());
-  SourceSpan span = named_ref->identifier->span();
-  auto last_name_component = named_ref->identifier->components.back()->span().data();
+  SourceSpan name_span = named_ref->identifier->span();
   auto name = CompileCompoundIdentifier(named_ref->identifier.get());
   if (!name)
     return false;
 
-  // Initialize the typector with just a name. The other parameters are filled in below.
-  auto type_ctor = std::make_unique<TypeConstructorOld>(
-      std::move(name.value()), nullptr, std::nullopt, nullptr, nullptr,
-      types::Nullability::kNonnullable, fidl::utils::Syntax::kNew);
-
-  // TODO(fxbug.dev/71536): this mess will get fixed once the new flat AST lands.
-  // Are there type parameters?
-  if (params != nullptr && !params->items.empty()) {
-    // There are currently only 3 generic types: box<TYPE>, vector<TYPE> and array<TYPE,SIZE>.
-    // That means the logic here can be very simple (for now): assume the first type parameter is
-    // always a maybe_type_ctor, and the second (if it exists) is a maybe_size.
-    auto builtin = span.data();
-    std::unique_ptr<raw::LayoutParameter> param;
-    if (params->items.size() >= 1) {
-      // Because all of the generic types we have today only take at most one nested type, it is
-      // okay to pass the name context through like this.  If we ever introduce generic types
-      // that take two or more types as parameters (ex: map<K,V>, or allowing things like
-      // struct{}<T>), we'll need extend the name contexts we pass down to their respective
-      // constructors.
-      param = std::move(params->items[0]);
-      auto name = Name::CreateDerived(this, span, context.full_name());
-
+  std::vector<std::unique_ptr<LayoutParameter>> params;
+  std::optional<SourceSpan> params_span;
+  if (raw_type_ctor->parameters) {
+    params_span = raw_type_ctor->parameters->span();
+    for (auto& p : raw_type_ctor->parameters->items) {
+      auto param = std::move(p);
+      auto span = param->span();
       switch (param->kind) {
+        case raw::LayoutParameter::Kind::kLiteral: {
+          auto literal_param = static_cast<raw::LiteralLayoutParameter*>(param.get());
+          std::unique_ptr<LiteralConstant> constant;
+          ConsumeLiteralConstant(literal_param->literal.get(), &constant);
+
+          std::unique_ptr<LayoutParameter> consumed =
+              std::make_unique<LiteralLayoutParameter>(std::move(constant), span);
+          params.push_back(std::move(consumed));
+          break;
+        }
         case raw::LayoutParameter::Kind::kType: {
           auto type_param = static_cast<raw::TypeLayoutParameter*>(param.get());
-          if (!ConsumeTypeConstructorNew(std::move(type_param->type_ctor), name,
-                                         &type_ctor->maybe_arg_type_ctor))
+          std::unique_ptr<TypeConstructorNew> type_ctor;
+          auto nested_name = Name::CreateDerived(this, name_span, context.full_name());
+          if (!ConsumeTypeConstructorNew(std::move(type_param->type_ctor), nested_name, &type_ctor))
             return false;
+
+          std::unique_ptr<LayoutParameter> consumed =
+              std::make_unique<TypeLayoutParameter>(std::move(type_ctor), span);
+          params.push_back(std::move(consumed));
           break;
         }
         case raw::LayoutParameter::Kind::kIdentifier: {
-          auto type_param = static_cast<raw::IdentifierLayoutParameter*>(param.get());
-          auto inner_name = Name::CreateSourced(this, type_param->identifier->span());
-          type_ctor->maybe_arg_type_ctor = std::make_unique<TypeConstructorOld>(
-              inner_name,
-              /*maybe_arg_type_ctor=*/nullptr,
-              /*handle_subtype_identifier=*/std::nullopt,
-              /*handle_rights=*/nullptr,
-              /*maybe_size=*/nullptr, types::Nullability::kNonnullable, fidl::utils::Syntax::kNew);
+          auto id_param = static_cast<raw::IdentifierLayoutParameter*>(param.get());
+          auto name = CompileCompoundIdentifier(id_param->identifier.get());
+          if (!name)
+            return false;
+
+          std::unique_ptr<LayoutParameter> consumed =
+              std::make_unique<IdentifierLayoutParameter>(std::move(name.value()), span);
+          params.push_back(std::move(consumed));
           break;
         }
-        case raw::LayoutParameter::Kind::kLiteral: {
-          assert(false && "the first type parameter must be a type, not a value");
-          break;
-        }
-      }
-    }
-    if (builtin == "array" && params->items.size() >= 2) {
-      param = std::move(params->items[1]);
-      switch (param->kind) {
-        case raw::LayoutParameter::Kind::kIdentifier: {
-          auto type_param = static_cast<raw::IdentifierLayoutParameter*>(param.get());
-          type_ctor->maybe_size = std::make_unique<Constant>(Constant::Kind::kIdentifier,
-                                                             type_param->identifier->span());
-          break;
-        }
-        case raw::LayoutParameter::Kind::kLiteral: {
-          auto type_param = static_cast<raw::LiteralLayoutParameter*>(param.get());
-          ConsumeConstant(std::move(type_param->literal), &type_ctor->maybe_size);
-          break;
-        }
-        default: {
-          assert(false && "the second type parameter must be a numeric value");
-        }
       }
     }
   }
 
-  // The rest of this function deals with properly applying constraints in two
-  // contexts: the special "handle" case where constraints must be of the form
-  // [HANDLE_SUBTYPE, HANDLE_RIGHTS, OPTIONAL], and the "default" context, which
-  // accepts constraints of [SIZE, OPTIONAL].
-  // TODO(fxbug.dev/68667): handle client and server end types when they exist.
-  // TODO(fxbug.dev/71536): The following code makes one assumption that we may
-  //  not want to keep: that the constraint optional cannot be overwritten.  In
-  //  other words, even if in a FIDL file "optional" is defined to have some
-  //  meaining, we still assume the word "optional" does not inherit that
-  //  meaning when used in constraints.  For example, consider this FIDL:
-  //
-  //    const optional uint8 = 3;
-  //    alias foo = vector<bool>:optional; // <- What does this "optional" mean?
-  //
-  //  While in the future we may like for optional to resolve to "3" in such
-  //  cases, for now this override is ignored, and "optional" always means
-  //  "optional" in the context of constraints.
-  if (last_name_component == "handle") {
-    // The parser currently only applies handle-like constraints onto types that
-    // specifically end in the word "handle" ("handle" or "zx.handle"), and does
-    // not do so to their aliases.  We replicate that behavior here.
-    if (num_constraints > 3) {
-      Fail(ErrConstraintsOverflow, type_ctor->name, size_t(3), num_constraints);
-      return false;
-    }
-    if (num_constraints == 1) {
-      // The lone constraint MUST be either one of a subtype OR an "optional."
-      if (IsOptionalConstraint(type_ctor, constraints->items[0])) {
-        type_ctor->nullability = types::Nullability::kNullable;
-      } else {
-        type_ctor->handle_subtype_identifier =
-            Name::CreateSourced(this, constraints->items[0]->span());
-      }
-    } else if (num_constraints == 2) {
-      // The first constraint MUST be a subtype, the second MUST be a handle
-      // rights OR an "optional."
-      type_ctor->handle_subtype_identifier =
-          Name::CreateSourced(this, constraints->items[0]->span());
-      if (IsOptionalConstraint(type_ctor, constraints->items[1])) {
-        type_ctor->nullability = types::Nullability::kNullable;
-      } else if (!ConsumeConstant(std::move(constraints->items[1]), &type_ctor->handle_rights)) {
+  std::vector<std::unique_ptr<Constant>> constraints;
+  std::optional<SourceSpan> constraints_span;
+  if (raw_type_ctor->constraints) {
+    constraints_span = raw_type_ctor->constraints->span();
+    for (auto& c : raw_type_ctor->constraints->items) {
+      std::unique_ptr<Constant> constraint;
+      if (!ConsumeConstant(std::move(c), &constraint))
         return false;
-      }
-    } else if (num_constraints == 3) {
-      // The first constraint MUST be a subtype, the second MUST be a handle
-      // rights, and the third MUST be an "optional."
-      type_ctor->handle_subtype_identifier =
-          Name::CreateSourced(this, constraints->items[0]->span());
-      if (!ConsumeConstant(std::move(constraints->items[1]), &type_ctor->handle_rights)) {
-        return false;
-      }
-      if (IsOptionalConstraint(type_ctor, constraints->items[2])) {
-        type_ctor->nullability = types::Nullability::kNullable;
-      } else {
-        Fail(ErrConstraintOptionalMisspelled, type_ctor->name,
-             constraints->items[2]->span().data());
-        return false;
-      }
-    }
-  } else {
-    // In the non-handle case, constraints are interpreted to be of the
-    // [SIZE, OPTIONAL] format.
-    if (num_constraints > 2) {
-      Fail(ErrConstraintsOverflow, type_ctor->name, size_t(2), num_constraints);
-      return false;
-    }
-    if (num_constraints == 1) {
-      // The lone constraint MUST be either one of a size OR an "optional."
-      if (IsOptionalConstraint(type_ctor, constraints->items[0])) {
-        type_ctor->nullability = types::Nullability::kNullable;
-      } else if (!ConsumeConstant(std::move(constraints->items[0]), &type_ctor->maybe_size)) {
-        return false;
-      }
-    } else if (num_constraints == 2) {
-      // The first constraint MUST be a size, the second MUST be "optional."
-      if (!ConsumeConstant(std::move(constraints->items[0]), &type_ctor->maybe_size)) {
-        return false;
-      }
-      if (IsOptionalConstraint(type_ctor, constraints->items[1])) {
-        type_ctor->nullability = types::Nullability::kNullable;
-      } else {
-        Fail(ErrConstraintOptionalMisspelled, type_ctor->name,
-             constraints->items[1]->span().data());
-        return false;
-      }
-      type_ctor->nullability = types::Nullability::kNullable;
+      constraints.push_back(std::move(constraint));
     }
   }
 
-  if (out_type)
-    *out_type = std::move(type_ctor);
+  assert(out_type_ctor && "out type ctors should always be provided for a named type ctor");
+  *out_type_ctor = std::make_unique<TypeConstructorNew>(
+      std::move(name.value()),
+      std::make_unique<LayoutParameterList>(std::move(params), params_span),
+      std::make_unique<TypeConstraints>(std::move(constraints), constraints_span));
   return true;
 }
 
 bool Library::ConsumeTypeConstructor(raw::TypeConstructor raw_type_ctor, const Name& context,
-                                     std::unique_ptr<TypeConstructorOld>* out_type) {
+                                     TypeConstructor* out_type) {
   return std::visit(fidl::utils::matchers{
                         [&, this](std::unique_ptr<raw::TypeConstructorOld> e) -> bool {
-                          return ConsumeTypeConstructorOld(std::move(e), out_type);
+                          std::unique_ptr<TypeConstructorOld> out;
+                          bool result = ConsumeTypeConstructorOld(std::move(e), &out);
+                          *out_type = std::move(out);
+                          return result;
                         },
                         [&, this](std::unique_ptr<raw::TypeConstructorNew> e) -> bool {
-                          return ConsumeTypeConstructorNew(std::move(e), context, out_type);
+                          std::unique_ptr<TypeConstructorNew> out;
+                          bool result = ConsumeTypeConstructorNew(std::move(e), context, &out);
+                          *out_type = std::move(out);
+                          return result;
                         },
                     },
                     std::move(raw_type_ctor));
@@ -2450,6 +2932,7 @@
 void Library::ConsumeTypeDecl(std::unique_ptr<raw::TypeDecl> type_decl) {
   auto name = Name::CreateSourced(this, type_decl->identifier->span());
   auto& layout_ref = type_decl->type_ctor->layout_ref;
+  // TODO(fxbug.dev/7807)
   if (layout_ref->kind == raw::LayoutReference::Kind::kNamed) {
     auto named_ref = static_cast<raw::NamedLayoutReference*>(layout_ref.get());
     Fail(ErrNewTypesNotAllowed, name, named_ref->span().data());
@@ -2667,12 +3150,12 @@
     return false;
   }
 
-  const TypeConstructorOld* const_type_ctor = nullptr;
+  const Type* const_type = nullptr;
   const ConstantValue* const_val = nullptr;
   switch (decl->kind) {
     case Decl::Kind::kConst: {
       auto const_decl = static_cast<Const*>(decl);
-      const_type_ctor = const_decl->type_ctor.get();
+      const_type = GetType(const_decl->type_ctor);
       const_val = &const_decl->value->Value();
       break;
     }
@@ -2680,7 +3163,7 @@
       // If there is no member name, fallthrough to default.
       if (auto member_name = identifier_constant->name.member_name(); member_name) {
         auto enum_decl = static_cast<Enum*>(decl);
-        const_type_ctor = enum_decl->subtype_ctor.get();
+        const_type = GetType(enum_decl->subtype_ctor);
         for (auto& member : enum_decl->members) {
           if (member.name.data() == member_name) {
             const_val = &member.value->Value();
@@ -2698,7 +3181,7 @@
       // If there is no member name, fallthrough to default.
       if (auto member_name = identifier_constant->name.member_name(); member_name) {
         auto bits_decl = static_cast<Bits*>(decl);
-        const_type_ctor = bits_decl->subtype_ctor.get();
+        const_type = GetType(bits_decl->subtype_ctor);
         for (auto& member : bits_decl->members) {
           if (member.name.data() == member_name) {
             const_val = &member.value->Value();
@@ -2714,17 +3197,17 @@
     }
     default: {
       return Fail(ErrExpectedValueButGotType, identifier_constant->name.span(),
-                  identifier_constant);
+                  identifier_constant->name);
     }
   }
 
   assert(const_val && "Compiler bug: did not set const_val");
-  assert(const_type_ctor && "Compiler bug: did not set const_type_ctor");
+  assert(const_type && "Compiler bug: did not set const_type");
 
   std::unique_ptr<ConstantValue> resolved_val;
   switch (type->kind) {
     case Type::Kind::kString: {
-      if (!TypeIsConvertibleTo(const_type_ctor->type, type))
+      if (!TypeIsConvertibleTo(const_type, type))
         goto fail_cannot_convert;
 
       if (!const_val->Convert(ConstantValue::Kind::kString, &resolved_val))
@@ -2743,14 +3226,14 @@
       switch (identifier_type->type_decl->kind) {
         case Decl::Kind::kEnum: {
           auto enum_decl = static_cast<const Enum*>(identifier_type->type_decl);
-          assert(enum_decl->subtype_ctor->type->kind == Type::Kind::kPrimitive);
-          primitive_type = static_cast<const PrimitiveType*>(enum_decl->subtype_ctor->type);
+          assert(GetType(enum_decl->subtype_ctor)->kind == Type::Kind::kPrimitive);
+          primitive_type = static_cast<const PrimitiveType*>(GetType(enum_decl->subtype_ctor));
           break;
         }
         case Decl::Kind::kBits: {
           auto bits_decl = static_cast<const Bits*>(identifier_type->type_decl);
-          assert(bits_decl->subtype_ctor->type->kind == Type::Kind::kPrimitive);
-          primitive_type = static_cast<const PrimitiveType*>(bits_decl->subtype_ctor->type);
+          assert(GetType(bits_decl->subtype_ctor)->kind == Type::Kind::kPrimitive);
+          primitive_type = static_cast<const PrimitiveType*>(GetType(bits_decl->subtype_ctor));
           break;
         }
         default: {
@@ -2764,8 +3247,8 @@
 
       switch (decl->kind) {
         case Decl::Kind::kConst: {
-          if (const_type_ctor->type->name != identifier_type->type_decl->name)
-            return fail_with_mismatched_type(const_type_ctor->type->name);
+          if (const_type->name != identifier_type->type_decl->name)
+            return fail_with_mismatched_type(const_type->name);
           break;
         }
         case Decl::Kind::kBits:
@@ -2792,7 +3275,7 @@
   return true;
 
 fail_cannot_convert:
-  return Fail(ErrCannotConvertConstantToType, identifier_constant, const_type_ctor, type);
+  return Fail(ErrCannotConvertConstantToType, identifier_constant, const_type, type);
 }
 
 bool Library::ResolveLiteralConstant(LiteralConstant* literal_constant, const Type* type) {
@@ -2965,9 +3448,9 @@
     return nullptr;
   switch (decl->kind) {
     case Decl::Kind::kBits:
-      return static_cast<const Bits*>(decl)->subtype_ctor->type;
+      return GetType(static_cast<const Bits*>(decl)->subtype_ctor);
     case Decl::Kind::kEnum:
-      return static_cast<const Enum*>(decl)->subtype_ctor->type;
+      return GetType(static_cast<const Enum*>(decl)->subtype_ctor);
     default:
       return type;
   }
@@ -3102,18 +3585,19 @@
 bool Library::DeclDependencies(const Decl* decl, std::set<const Decl*>* out_edges) {
   std::set<const Decl*> edges;
 
-  auto maybe_add_decl = [&edges](const TypeConstructorOld* type_ctor) {
-    const TypeConstructorOld* current = type_ctor;
+  auto maybe_add_decl = [&edges](TypeConstructorPtr type_ctor) {
+    TypeConstructorPtr current = type_ctor;
     for (;;) {
-      if (current->from_type_alias) {
-        assert(!current->maybe_arg_type_ctor &&
+      const auto& invocation = GetLayoutInvocation(current);
+      if (invocation.from_type_alias) {
+        assert(!invocation.element_type_resolved &&
                "Compiler bug: partial aliases should be disallowed");
-        edges.insert(current->from_type_alias->decl);
+        edges.insert(invocation.from_type_alias);
         return;
       }
 
-      const Type* type = current->type;
-      if (current->nullability == types::Nullability::kNullable)
+      const Type* type = GetType(current);
+      if (type->nullability == types::Nullability::kNullable)
         return;
 
       switch (type->kind) {
@@ -3135,8 +3619,8 @@
           return;
         case Type::Kind::kArray:
         case Type::Kind::kVector: {
-          if (current->maybe_arg_type_ctor) {
-            current = current->maybe_arg_type_ctor.get();
+          if (IsTypeConstructorDefined(invocation.element_type_raw)) {
+            current = invocation.element_type_raw;
             break;
           }
           // The type_ctor won't have an arg_type_ctor if the type is Bytes.
@@ -3156,10 +3640,11 @@
       }
     }
   };
+
   switch (decl->kind) {
     case Decl::Kind::kBits: {
       auto bits_decl = static_cast<const Bits*>(decl);
-      maybe_add_decl(bits_decl->subtype_ctor.get());
+      maybe_add_decl(GetTypeCtorAsPtr(bits_decl->subtype_ctor));
       for (const auto& member : bits_decl->members) {
         if (!AddConstantDependencies(member.value.get(), &edges)) {
           return false;
@@ -3169,7 +3654,7 @@
     }
     case Decl::Kind::kConst: {
       auto const_decl = static_cast<const Const*>(decl);
-      maybe_add_decl(const_decl->type_ctor.get());
+      maybe_add_decl(GetTypeCtorAsPtr(const_decl->type_ctor));
       if (!AddConstantDependencies(const_decl->value.get(), &edges)) {
         return false;
       }
@@ -3177,7 +3662,7 @@
     }
     case Decl::Kind::kEnum: {
       auto enum_decl = static_cast<const Enum*>(decl);
-      maybe_add_decl(enum_decl->subtype_ctor.get());
+      maybe_add_decl(GetTypeCtorAsPtr(enum_decl->subtype_ctor));
       for (const auto& member : enum_decl->members) {
         if (!AddConstantDependencies(member.value.get(), &edges)) {
           return false;
@@ -3204,20 +3689,20 @@
     }
     case Decl::Kind::kResource: {
       auto resource_decl = static_cast<const Resource*>(decl);
-      maybe_add_decl(resource_decl->subtype_ctor.get());
+      maybe_add_decl(GetTypeCtorAsPtr(resource_decl->subtype_ctor));
       break;
     }
     case Decl::Kind::kService: {
       auto service_decl = static_cast<const Service*>(decl);
       for (const auto& member : service_decl->members) {
-        maybe_add_decl(member.type_ctor.get());
+        maybe_add_decl(GetTypeCtorAsPtr(member.type_ctor));
       }
       break;
     }
     case Decl::Kind::kStruct: {
       auto struct_decl = static_cast<const Struct*>(decl);
       for (const auto& member : struct_decl->members) {
-        maybe_add_decl(member.type_ctor.get());
+        maybe_add_decl(GetTypeCtorAsPtr(member.type_ctor));
         if (member.maybe_default_value) {
           if (!AddConstantDependencies(member.maybe_default_value.get(), &edges)) {
             return false;
@@ -3231,7 +3716,7 @@
       for (const auto& member : table_decl->members) {
         if (!member.maybe_used)
           continue;
-        maybe_add_decl(member.maybe_used->type_ctor.get());
+        maybe_add_decl(GetTypeCtorAsPtr(member.maybe_used->type_ctor));
         if (member.maybe_used->maybe_default_value) {
           if (!AddConstantDependencies(member.maybe_used->maybe_default_value.get(), &edges)) {
             return false;
@@ -3245,13 +3730,13 @@
       for (const auto& member : union_decl->members) {
         if (!member.maybe_used)
           continue;
-        maybe_add_decl(member.maybe_used->type_ctor.get());
+        maybe_add_decl(GetTypeCtorAsPtr(member.maybe_used->type_ctor));
       }
       break;
     }
     case Decl::Kind::kTypeAlias: {
       auto type_alias_decl = static_cast<const TypeAlias*>(decl);
-      maybe_add_decl(type_alias_decl->partial_type_ctor.get());
+      maybe_add_decl(GetTypeCtorAsPtr(type_alias_decl->partial_type_ctor));
     }
   }  // switch
   *out_edges = std::move(edges);
@@ -3569,7 +4054,7 @@
       const auto* struct_decl = static_cast<const Struct*>(decl);
       if (struct_decl->resourceness == types::Resourceness::kValue) {
         for (const auto& member : struct_decl->members) {
-          if (EffectiveResourceness(member.type_ctor->type) == types::Resourceness::kResource) {
+          if (EffectiveResourceness(GetType(member.type_ctor)) == types::Resourceness::kResource) {
             library_->reporter_->Report(ErrTypeMustBeResource, struct_decl->name.span(),
                                         struct_decl->name, member.name.data(),
                                         std::string_view("struct"), struct_decl->name);
@@ -3584,7 +4069,7 @@
         for (const auto& member : table_decl->members) {
           if (member.maybe_used) {
             const auto& used = *member.maybe_used;
-            if (EffectiveResourceness(used.type_ctor->type) == types::Resourceness::kResource) {
+            if (EffectiveResourceness(GetType(used.type_ctor)) == types::Resourceness::kResource) {
               library_->reporter_->Report(ErrTypeMustBeResource, table_decl->name.span(),
                                           table_decl->name, used.name.data(),
                                           std::string_view("table"), table_decl->name);
@@ -3600,7 +4085,7 @@
         for (const auto& member : union_decl->members) {
           if (member.maybe_used) {
             const auto& used = *member.maybe_used;
-            if (EffectiveResourceness(used.type_ctor->type) == types::Resourceness::kResource) {
+            if (EffectiveResourceness(GetType(used.type_ctor)) == types::Resourceness::kResource) {
               library_->reporter_->Report(ErrTypeMustBeResource, union_decl->name.span(),
                                           union_decl->name, used.name.data(),
                                           std::string_view("union"), union_decl->name);
@@ -3715,7 +4200,7 @@
   switch (decl->kind) {
     case Decl::Kind::kStruct:
       for (const auto& member : static_cast<const Struct*>(decl)->members) {
-        if (EffectiveResourceness(member.type_ctor->type) == types::Resourceness::kResource) {
+        if (EffectiveResourceness(GetType(member.type_ctor)) == types::Resourceness::kResource) {
           effective_resourceness_[decl] = types::Resourceness::kResource;
           return types::Resourceness::kResource;
         }
@@ -3725,7 +4210,7 @@
       for (const auto& member : static_cast<const Table*>(decl)->members) {
         const auto& used = member.maybe_used;
         if (used &&
-            EffectiveResourceness(used->type_ctor->type) == types::Resourceness::kResource) {
+            EffectiveResourceness(GetType(used->type_ctor)) == types::Resourceness::kResource) {
           effective_resourceness_[decl] = types::Resourceness::kResource;
           return types::Resourceness::kResource;
         }
@@ -3735,7 +4220,7 @@
       for (const auto& member : static_cast<const Union*>(decl)->members) {
         const auto& used = member.maybe_used;
         if (used &&
-            EffectiveResourceness(used->type_ctor->type) == types::Resourceness::kResource) {
+            EffectiveResourceness(GetType(used->type_ctor)) == types::Resourceness::kResource) {
           effective_resourceness_[decl] = types::Resourceness::kResource;
           return types::Resourceness::kResource;
         }
@@ -3750,16 +4235,16 @@
 }
 
 bool Library::CompileBits(Bits* bits_declaration) {
-  if (!CompileTypeConstructor(bits_declaration->subtype_ctor.get()))
+  if (!CompileTypeConstructor(&bits_declaration->subtype_ctor))
     return false;
 
-  if (bits_declaration->subtype_ctor->type->kind != Type::Kind::kPrimitive) {
+  if (GetType(bits_declaration->subtype_ctor)->kind != Type::Kind::kPrimitive) {
     return Fail(ErrBitsTypeMustBeUnsignedIntegralPrimitive, *bits_declaration,
-                bits_declaration->subtype_ctor->type);
+                GetType(bits_declaration->subtype_ctor));
   }
 
   // Validate constants.
-  auto primitive_type = static_cast<const PrimitiveType*>(bits_declaration->subtype_ctor->type);
+  auto primitive_type = static_cast<const PrimitiveType*>(GetType(bits_declaration->subtype_ctor));
   switch (primitive_type->subtype) {
     case types::PrimitiveSubtype::kUint8: {
       uint8_t mask;
@@ -3797,7 +4282,7 @@
     case types::PrimitiveSubtype::kFloat32:
     case types::PrimitiveSubtype::kFloat64:
       return Fail(ErrBitsTypeMustBeUnsignedIntegralPrimitive, *bits_declaration,
-                  bits_declaration->subtype_ctor->type);
+                  GetType(bits_declaration->subtype_ctor));
   }
 
   {
@@ -3814,9 +4299,9 @@
 }
 
 bool Library::CompileConst(Const* const_declaration) {
-  if (!CompileTypeConstructor(const_declaration->type_ctor.get()))
+  if (!CompileTypeConstructor(&const_declaration->type_ctor))
     return false;
-  const auto* const_type = const_declaration->type_ctor->type;
+  const auto* const_type = GetType(const_declaration->type_ctor);
   if (!TypeCanBeConst(const_type)) {
     return Fail(ErrInvalidConstantType, *const_declaration, const_type);
   }
@@ -3827,16 +4312,16 @@
 }
 
 bool Library::CompileEnum(Enum* enum_declaration) {
-  if (!CompileTypeConstructor(enum_declaration->subtype_ctor.get()))
+  if (!CompileTypeConstructor(&enum_declaration->subtype_ctor))
     return false;
 
-  if (enum_declaration->subtype_ctor->type->kind != Type::Kind::kPrimitive) {
+  if (GetType(enum_declaration->subtype_ctor)->kind != Type::Kind::kPrimitive) {
     return Fail(ErrEnumTypeMustBeIntegralPrimitive, *enum_declaration,
-                enum_declaration->subtype_ctor->type);
+                GetType(enum_declaration->subtype_ctor));
   }
 
   // Validate constants.
-  auto primitive_type = static_cast<const PrimitiveType*>(enum_declaration->subtype_ctor->type);
+  auto primitive_type = static_cast<const PrimitiveType*>(GetType(enum_declaration->subtype_ctor));
   enum_declaration->type = primitive_type;
   switch (primitive_type->subtype) {
     case types::PrimitiveSubtype::kInt8: {
@@ -3899,7 +4384,7 @@
     case types::PrimitiveSubtype::kFloat32:
     case types::PrimitiveSubtype::kFloat64:
       return Fail(ErrEnumTypeMustBeIntegralPrimitive, *enum_declaration,
-                  enum_declaration->subtype_ctor->type);
+                  GetType(enum_declaration->subtype_ctor));
   }
 
   return true;
@@ -3909,12 +4394,12 @@
 
 bool Library::CompileResource(Resource* resource_declaration) {
   Scope<std::string_view> scope;
-  if (!CompileTypeConstructor(resource_declaration->subtype_ctor.get()))
+  if (!CompileTypeConstructor(&resource_declaration->subtype_ctor))
     return false;
 
-  if (resource_declaration->subtype_ctor->type->kind != Type::Kind::kPrimitive) {
+  if (GetType(resource_declaration->subtype_ctor)->kind != Type::Kind::kPrimitive) {
     return Fail(ErrEnumTypeMustBeIntegralPrimitive, *resource_declaration,
-                resource_declaration->subtype_ctor->type);
+                GetType(resource_declaration->subtype_ctor));
   }
 
   for (auto& property : resource_declaration->properties) {
@@ -3923,7 +4408,7 @@
       return Fail(ErrDuplicateResourcePropertyName, property.name,
                   name_result.previous_occurrence());
 
-    if (!CompileTypeConstructor(property.type_ctor.get()))
+    if (!CompileTypeConstructor(&property.type_ctor))
       return false;
   }
   return true;
@@ -4004,7 +4489,7 @@
 
 bool Library::CompileService(Service* service_decl) {
   Scope<std::string> scope;
-  for (const auto& member : service_decl->members) {
+  for (auto& member : service_decl->members) {
     const auto original_name = member.name.data();
     const auto canonical_name = utils::canonicalize(original_name);
     const auto name_result = scope.Insert(canonical_name, member.name);
@@ -4016,14 +4501,23 @@
       return Fail(ErrDuplicateServiceMemberNameCanonical, member.name, original_name,
                   previous_span.data(), previous_span, canonical_name);
     }
-    // TODO: in the new syntax we check that the service member is a type, but
-    // don't validate that it's client_end or server_end (because they don't exist yet)
-    auto expected_category = member.type_ctor->syntax == fidl::utils::Syntax::kOld
-                                 ? AllowedCategories::kProtocolOnly
-                                 : AllowedCategories::kTypeOnly;
-    if (!CompileTypeConstructorAllowing(member.type_ctor.get(), expected_category))
+    if (!CompileTypeConstructor(&member.type_ctor))
       return false;
-    if (member.type_ctor->nullability != types::Nullability::kNonnullable)
+    // There's a mismatch between the "default" allowed categories and what is actually allowed
+    // in this context: in the new syntax, nothing changes. In the old syntax, we are more
+    // restrictive in this context, requiring kProtocolOnly rather than kTypeOrProtocol (which is
+    // the default for TypeConstructorOld).
+    bool ok =
+        std::visit(fidl::utils::matchers{
+                       [this](const std::unique_ptr<TypeConstructorOld>& type_ctor) -> bool {
+                         return VerifyTypeCategory(type_ctor->type, type_ctor->name.span(),
+                                                   AllowedCategories::kProtocolOnly);
+                       },
+                       [](const std::unique_ptr<TypeConstructorNew>& t) -> bool { return true; }},
+                   member.type_ctor);
+    if (!ok)
+      return false;
+    if (GetType(member.type_ctor)->nullability != types::Nullability::kNonnullable)
       return Fail(ErrNullableServiceMember, member.name);
   }
   return true;
@@ -4032,7 +4526,7 @@
 bool Library::CompileStruct(Struct* struct_declaration) {
   Scope<std::string> scope;
   DeriveResourceness derive_resourceness(&struct_declaration->resourceness);
-  for (const auto& member : struct_declaration->members) {
+  for (auto& member : struct_declaration->members) {
     const auto original_name = member.name.data();
     const auto canonical_name = utils::canonicalize(original_name);
     const auto name_result = scope.Insert(canonical_name, member.name);
@@ -4049,12 +4543,12 @@
                   member.name, original_name, previous_span.data(), previous_span, canonical_name);
     }
 
-    if (!CompileTypeConstructor(member.type_ctor.get()))
+    if (!CompileTypeConstructor(&member.type_ctor))
       return false;
     assert(!(struct_declaration->is_request_or_response && member.maybe_default_value) &&
            "method parameters cannot have default values");
     if (member.maybe_default_value) {
-      const auto* default_value_type = member.type_ctor->type;
+      const auto* default_value_type = GetType(member.type_ctor);
       if (!TypeCanBeConst(default_value_type)) {
         return Fail(ErrInvalidStructMemberType, *struct_declaration, NameIdentifier(member.name),
                     default_value_type);
@@ -4063,7 +4557,7 @@
         return false;
       }
     }
-    derive_resourceness.AddType(member.type_ctor->type);
+    derive_resourceness.AddType(GetType(member.type_ctor));
   }
 
   return true;
@@ -4073,14 +4567,14 @@
   Scope<std::string> name_scope;
   Ordinal64Scope ordinal_scope;
 
-  for (const auto& member : table_declaration->members) {
+  for (auto& member : table_declaration->members) {
     const auto ordinal_result = ordinal_scope.Insert(member.ordinal->value, member.ordinal->span());
     if (!ordinal_result.ok()) {
       return Fail(ErrDuplicateTableFieldOrdinal, member.ordinal->span(),
                   ordinal_result.previous_occurrence());
     }
     if (member.maybe_used) {
-      const auto& member_used = *member.maybe_used;
+      auto& member_used = *member.maybe_used;
       const auto original_name = member_used.name.data();
       const auto canonical_name = utils::canonicalize(original_name);
       const auto name_result = name_scope.Insert(canonical_name, member_used.name);
@@ -4092,9 +4586,12 @@
         return Fail(ErrDuplicateTableFieldNameCanonical, member_used.name, original_name,
                     previous_span.data(), previous_span, canonical_name);
       }
-      if (!CompileTypeConstructor(member_used.type_ctor.get())) {
+      if (!CompileTypeConstructor(&member_used.type_ctor)) {
         return false;
       }
+      if (GetType(member_used.type_ctor)->nullability != types::Nullability::kNonnullable) {
+        return Fail(ErrNullableTableMember, member_used.name);
+      }
     }
   }
 
@@ -4131,10 +4628,13 @@
                     previous_span.data(), previous_span, canonical_name);
       }
 
-      if (!CompileTypeConstructor(member_used.type_ctor.get())) {
+      if (!CompileTypeConstructor(const_cast<TypeConstructor*>(&member_used.type_ctor))) {
         return false;
       }
-      derive_resourceness.AddType(member_used.type_ctor->type);
+      if (GetType(member_used.type_ctor)->nullability != types::Nullability::kNonnullable) {
+        return Fail(ErrNullableUnionMember, member_used.name);
+      }
+      derive_resourceness.AddType(GetType(member_used.type_ctor));
     }
   }
 
@@ -4161,7 +4661,7 @@
 }
 
 bool Library::CompileTypeAlias(TypeAlias* type_alias) {
-  if (type_alias->partial_type_ctor->name == type_alias->name)
+  if (GetName(type_alias->partial_type_ctor) == type_alias->name)
     // fidlc's current semantics for cases like `alias foo = foo;` is to
     // include the LHS in the scope while compiling the RHS. Note that because
     // of an interaction with a fidlc scoping bug that prevents shadowing builtins,
@@ -4171,7 +4671,7 @@
     // resolve the RHS. To avoid inconsistent semantics, we need to manually
     // catch this case and fail.
     return Fail(ErrIncludeCycle);
-  return CompileTypeConstructor(type_alias->partial_type_ctor.get());
+  return CompileTypeConstructor(&type_alias->partial_type_ctor);
 }
 
 bool Library::Compile() {
@@ -4219,49 +4719,64 @@
   return reporter_->errors().size() == 0;
 }
 
-bool Library::CompileTypeConstructor(TypeConstructorOld* type_ctor) {
-  if (type_ctor->syntax == fidl::utils::Syntax::kNew) {
-    return CompileTypeConstructorAllowing(type_ctor, AllowedCategories::kTypeOnly);
-  }
-  return CompileTypeConstructorAllowing(type_ctor, AllowedCategories::kTypeOrProtocol);
+bool Library::CompileTypeConstructor(TypeConstructor* type_ctor) {
+  return std::visit(fidl::utils::matchers{
+                        [&, this](const std::unique_ptr<TypeConstructorOld>& type_ctor) -> bool {
+                          return CompileTypeConstructorOld(type_ctor.get());
+                        },
+                        [&, this](const std::unique_ptr<TypeConstructorNew>& type_ctor) -> bool {
+                          return CompileTypeConstructorNew(type_ctor.get());
+                        },
+                    },
+                    *type_ctor);
 }
 
-bool Library::CompileTypeConstructorAllowing(TypeConstructorOld* type_ctor,
-                                             AllowedCategories category) {
+bool Library::CompileTypeConstructorOld(TypeConstructorOld* type_ctor) {
   if (!typespace_->Create(LibraryMediator(this), type_ctor->name, type_ctor->maybe_arg_type_ctor,
                           type_ctor->handle_subtype_identifier, type_ctor->handle_rights,
                           type_ctor->maybe_size, type_ctor->nullability, &type_ctor->type,
-                          &type_ctor->from_type_alias))
+                          &type_ctor->resolved_params))
     return false;
 
   // postcondition: compilation sets the Type of the TypeConstructor
   assert(type_ctor->type && "type constructors' type not resolved after compilation");
-  return VerifyTypeCategory(type_ctor, category);
+  return VerifyTypeCategory(type_ctor->type, type_ctor->name.span(),
+                            AllowedCategories::kTypeOrProtocol);
 }
 
-bool Library::VerifyTypeCategory(TypeConstructorOld* type_ctor, AllowedCategories category) {
-  assert(type_ctor->type && "CompileTypeConstructor did not set Type");
-  if (type_ctor->type->kind != Type::Kind::kIdentifier) {
+bool Library::CompileTypeConstructorNew(TypeConstructorNew* type_ctor) {
+  if (!typespace_->Create(LibraryMediator(this), type_ctor->name, type_ctor->parameters,
+                          type_ctor->constraints, &type_ctor->type, &type_ctor->resolved_params))
+    return false;
+
+  // // postcondition: compilation sets the Type of the TypeConstructor
+  assert(type_ctor->type && "type constructors' type not resolved after compilation");
+  return VerifyTypeCategory(type_ctor->type, type_ctor->name.span(), AllowedCategories::kTypeOnly);
+  return false;
+}
+
+bool Library::VerifyTypeCategory(const Type* type, std::optional<SourceSpan> span,
+                                 AllowedCategories category) {
+  assert(type && "CompileTypeConstructor did not set Type");
+  if (type->kind != Type::Kind::kIdentifier) {
     // we assume that all non-identifier types (i.e. builtins) are actually
     // types (and not e.g. protocols or services).
-    return category == AllowedCategories::kProtocolOnly
-               ? Fail(ErrCannotUseType, type_ctor->name.span())
-               : true;
+    return category == AllowedCategories::kProtocolOnly ? Fail(ErrCannotUseType, span) : true;
   }
 
-  auto identifier_type = static_cast<const IdentifierType*>(type_ctor->type);
+  auto identifier_type = static_cast<const IdentifierType*>(type);
   switch (identifier_type->type_decl->kind) {
     // services are never allowed in any context
     case Decl::Kind::kService:
-      return Fail(ErrCannotUseService, type_ctor->name.span());
+      return Fail(ErrCannotUseService, span);
       break;
     case Decl::Kind::kProtocol:
       if (category == AllowedCategories::kTypeOnly)
-        return Fail(ErrCannotUseProtocol, type_ctor->name.span());
+        return Fail(ErrCannotUseProtocol, span);
       break;
     default:
       if (category == AllowedCategories::kProtocolOnly)
-        return Fail(ErrCannotUseType, type_ctor->name.span());
+        return Fail(ErrCannotUseType, span);
       break;
   }
   return true;
@@ -4269,7 +4784,8 @@
 
 bool Library::ResolveHandleRightsConstant(Resource* resource, Constant* constant,
                                           const HandleRights** out_rights) {
-  if (!resource->subtype_ctor || resource->subtype_ctor->name.full_name() != "uint32") {
+  if (!IsTypeConstructorDefined(resource->subtype_ctor) ||
+      GetName(resource->subtype_ctor).full_name() != "uint32") {
     return Fail(ErrResourceMustBeUint32Derived, resource->name);
   }
 
@@ -4278,16 +4794,16 @@
     return Fail(ErrResourceMissingRightsProperty, resource->name);
   }
 
-  Decl* rights_decl = LookupDeclByName(rights_property->type_ctor->name);
+  Decl* rights_decl = LookupDeclByName(GetName(rights_property->type_ctor));
   if (!rights_decl || rights_decl->kind != Decl::Kind::kBits) {
     return Fail(ErrResourceRightsPropertyMustReferToBits, resource->name);
   }
 
-  if (!rights_property->type_ctor->type) {
-    if (!CompileTypeConstructor(rights_property->type_ctor.get()))
+  if (!GetType(rights_property->type_ctor)) {
+    if (!CompileTypeConstructor(&rights_property->type_ctor))
       return false;
   }
-  const Type* rights_type = rights_property->type_ctor->type;
+  const Type* rights_type = GetType(rights_property->type_ctor);
 
   if (!ResolveConstant(constant, rights_type))
     return false;
@@ -4312,7 +4828,8 @@
   auto identifier_constant = static_cast<IdentifierConstant*>(constant.get());
   const Name& handle_subtype_identifier = identifier_constant->name;
 
-  if (!resource->subtype_ctor || resource->subtype_ctor->name.full_name() != "uint32") {
+  if (!IsTypeConstructorDefined(resource->subtype_ctor) ||
+      GetName(resource->subtype_ctor).full_name() != "uint32") {
     return Fail(ErrResourceMustBeUint32Derived, resource->name);
   }
   auto subtype_property = resource->LookupProperty("subtype");
@@ -4320,16 +4837,16 @@
     return Fail(ErrResourceMissingSubtypeProperty, resource->name);
   }
 
-  Decl* subtype_decl = LookupDeclByName(subtype_property->type_ctor->name);
+  Decl* subtype_decl = LookupDeclByName(GetName(subtype_property->type_ctor));
   if (!subtype_decl || subtype_decl->kind != Decl::Kind::kEnum) {
     return Fail(ErrResourceSubtypePropertyMustReferToEnum, resource->name);
   }
 
-  if (!subtype_property->type_ctor->type) {
-    if (!CompileTypeConstructor(subtype_property->type_ctor.get()))
+  if (!GetType(subtype_property->type_ctor)) {
+    if (!CompileTypeConstructor(&subtype_property->type_ctor))
       return false;
   }
-  const Type* subtype_type = subtype_property->type_ctor->type;
+  const Type* subtype_type = GetType(subtype_property->type_ctor);
 
   auto* subtype_enum = static_cast<Enum*>(subtype_decl);
   for (const auto& member : subtype_enum->members) {
@@ -4358,7 +4875,7 @@
     }
   }
   if (!size_constant->IsResolved()) {
-    return Fail(ErrCouldNotParseSizeBound, size_constant->span);
+    return false;
   }
   if (out_size) {
     *out_size = static_cast<const Size*>(&size_constant->Value());
@@ -4378,8 +4895,7 @@
   for (const auto& member : decl->members) {
     assert(member.value != nullptr && "Compiler bug: member value is null!");
 
-    assert(decl->subtype_ctor->type && "Compiler bug: must compile subtype ctor first");
-    if (!ResolveConstant(member.value.get(), decl->subtype_ctor->type)) {
+    if (!ResolveConstant(member.value.get(), GetType(decl->subtype_ctor))) {
       return Fail(ErrCouldNotResolveMember, member.name, std::string(decl_type));
     }
 
@@ -4459,7 +4975,7 @@
 
   auto unknown_value = std::numeric_limits<MemberType>::max();
   for (const auto& member : enum_decl->members) {
-    if (!ResolveConstant(member.value.get(), enum_decl->subtype_ctor->type)) {
+    if (!ResolveConstant(member.value.get(), GetType(enum_decl->subtype_ctor))) {
       return Fail(ErrCouldNotResolveMember, member.name, std::string("enum"));
     }
     auto attributes = member.attributes.get();
@@ -4524,11 +5040,110 @@
   return std::make_unique<TypeConstructorOld>(
       Name::CreateIntrinsic("uint32"), nullptr /* maybe_arg_type */,
       std::optional<Name>() /* handle_subtype_identifier */, nullptr /* handle_rights */,
-      nullptr /* maybe_size */, types::Nullability::kNonnullable, fidl::utils::Syntax::kOld);
+      nullptr /* maybe_size */, types::Nullability::kNonnullable);
+}
+
+std::unique_ptr<TypeConstructorNew> TypeConstructorNew::CreateSizeType() {
+  std::vector<std::unique_ptr<LayoutParameter>> no_params;
+  std::vector<std::unique_ptr<Constant>> no_constraints;
+  return std::make_unique<TypeConstructorNew>(
+      Name::CreateIntrinsic("uint32"),
+      std::make_unique<LayoutParameterList>(std::move(no_params), std::nullopt /* span */),
+      std::make_unique<TypeConstraints>(std::move(no_constraints), std::nullopt /* span */));
+}
+
+bool LibraryMediator::ResolveParamAsType(const flat::TypeTemplate* layout,
+                                         const std::unique_ptr<LayoutParameter>& param,
+                                         const Type** out_type) const {
+  auto type_ctor = param->AsTypeCtor();
+  auto check = library_->reporter_->Checkpoint();
+  if (!type_ctor || !ResolveType(type_ctor)) {
+    // if there were no errors reported but we couldn't resolve to a type, it must
+    // mean that the parameter referred to a non-type, so report a new error here.
+    if (check.NoNewErrors()) {
+      return library_->Fail(ErrExpectedType, param->span);
+    }
+    // otherwise, there was an error during the type resolution process, so we
+    // should just report that rather than add an extra error here
+    return false;
+  }
+  *out_type = type_ctor->type;
+  return true;
+}
+
+bool LibraryMediator::ResolveParamAsSize(const flat::TypeTemplate* layout,
+                                         const std::unique_ptr<LayoutParameter>& param,
+                                         const Size** out_size) const {
+  // We could use param->AsConstant() here, leading to code similar to ResolveParamAsType.
+  // However, unlike ErrExpectedType, ErrExpectedValueButGotType requires a name to be
+  // reported, which would require doing a switch on the parameter kind anyway to find
+  // its Name. So we just handle all the cases ourselves from the start.
+  switch (param->kind) {
+    case LayoutParameter::Kind::kLiteral: {
+      auto literal_param = static_cast<LiteralLayoutParameter*>(param.get());
+      if (!ResolveSizeBound(literal_param->literal.get(), out_size))
+        return library_->Fail(ErrCouldNotParseSizeBound);
+      break;
+    }
+    case LayoutParameter::kType: {
+      auto type_param = static_cast<TypeLayoutParameter*>(param.get());
+      return library_->Fail(ErrExpectedValueButGotType, type_param->type_ctor->name);
+    }
+    case LayoutParameter::Kind::kIdentifier: {
+      auto ambig_param = static_cast<IdentifierLayoutParameter*>(param.get());
+      auto as_constant = ambig_param->AsConstant();
+      if (!ResolveSizeBound(as_constant, out_size))
+        return library_->Fail(ErrExpectedValueButGotType, ambig_param->name);
+      break;
+    }
+  }
+  assert(*out_size);
+  if ((*out_size)->value == 0)
+    return library_->Fail(ErrMustHaveNonZeroSize, param->span, layout);
+  return true;
+}
+
+bool LibraryMediator::ResolveConstraintAs(const std::unique_ptr<Constant>& constraint,
+                                          const std::vector<ConstraintKind>& interpretations,
+                                          Resource* resource, ResolvedConstraint* out) const {
+  for (const auto& constraint_kind : interpretations) {
+    out->kind = constraint_kind;
+    switch (constraint_kind) {
+      case ConstraintKind::kHandleSubtype: {
+        assert(resource &&
+               "Compiler bug: must pass resource if trying to resolve to handle subtype");
+        if (ResolveAsHandleSubtype(resource, constraint, &out->value.handle_subtype))
+          return true;
+        break;
+      }
+      case ConstraintKind::kHandleRights: {
+        assert(resource &&
+               "Compiler bug: must pass resource if trying to resolve to handle rights");
+        if (ResolveAsHandleRights(resource, constraint.get(), &(out->value.handle_rights)))
+          return true;
+        break;
+      }
+      case ConstraintKind::kSize: {
+        if (ResolveSizeBound(constraint.get(), &(out->value.size)))
+          return true;
+        break;
+      }
+      case ConstraintKind::kNullability: {
+        if (ResolveAsOptional(constraint.get()))
+          return true;
+        break;
+      }
+    }
+  }
+  return false;
 }
 
 bool LibraryMediator::ResolveType(TypeConstructorOld* type) const {
-  return library_->CompileTypeConstructor(type);
+  return library_->CompileTypeConstructorOld(type);
+}
+
+bool LibraryMediator::ResolveType(TypeConstructorNew* type) const {
+  return library_->CompileTypeConstructorNew(type);
 }
 
 bool LibraryMediator::ResolveSizeBound(Constant* size_constant, const Size** out_size) const {
@@ -4550,10 +5165,39 @@
   return library_->ResolveHandleRightsConstant(resource, constant, out_rights);
 }
 
+template <typename... Args>
+bool LibraryMediator::Fail(const ErrorDef<Args...>& err, const std::optional<SourceSpan>& span,
+                           const Args&... args) const {
+  return library_->Fail(err, span, args...);
+}
+
 Decl* LibraryMediator::LookupDeclByName(Name::Key name) const {
   return library_->LookupDeclByName(name);
 }
 
+TypeConstructorNew* LiteralLayoutParameter::AsTypeCtor() const { return nullptr; }
+TypeConstructorNew* TypeLayoutParameter::AsTypeCtor() const { return type_ctor.get(); }
+TypeConstructorNew* IdentifierLayoutParameter::AsTypeCtor() const {
+  if (!as_type_ctor) {
+    std::vector<std::unique_ptr<LayoutParameter>> no_params;
+    std::vector<std::unique_ptr<Constant>> no_constraints;
+    as_type_ctor = std::make_unique<TypeConstructorNew>(
+        name, std::make_unique<LayoutParameterList>(std::move(no_params), std::nullopt),
+        std::make_unique<TypeConstraints>(std::move(no_constraints), std::nullopt));
+  }
+
+  return as_type_ctor.get();
+}
+
+Constant* LiteralLayoutParameter::AsConstant() const { return literal.get(); }
+Constant* TypeLayoutParameter::AsConstant() const { return nullptr; }
+Constant* IdentifierLayoutParameter::AsConstant() const {
+  if (!as_constant) {
+    as_constant = std::make_unique<IdentifierConstant>(name, span);
+  }
+  return as_constant.get();
+}
+
 bool LibraryMediator::CompileDecl(Decl* decl) const { return library_->CompileDecl(decl); }
 
 }  // namespace flat
diff --git a/tools/fidl/fidlc/lib/json_generator.cc b/tools/fidl/fidlc/lib/json_generator.cc
index 4eef387..357102c 100644
--- a/tools/fidl/fidlc/lib/json_generator.cc
+++ b/tools/fidl/fidlc/lib/json_generator.cc
@@ -4,6 +4,9 @@
 
 #include "fidl/json_generator.h"
 
+#include "fidl/diagnostic_types.h"
+#include "fidl/flat/types.h"
+#include "fidl/flat_ast.h"
 #include "fidl/names.h"
 #include "fidl/types.h"
 
@@ -230,7 +233,7 @@
     GenerateObjectMember("location", NameSpan(value.name));
     if (value.attributes)
       GenerateObjectMember("maybe_attributes", value.attributes);
-    GenerateTypeAndFromTypeAlias(*value.subtype_ctor);
+    GenerateTypeAndFromTypeAlias(value.subtype_ctor);
     // TODO(fxbug.dev/7660): When all numbers are wrapped as string, we can simply
     // call GenerateObjectMember directly.
     GenerateObjectPunctuation(Position::kSubsequent);
@@ -257,7 +260,7 @@
     GenerateObjectMember("location", NameSpan(value.name));
     if (value.attributes)
       GenerateObjectMember("maybe_attributes", value.attributes);
-    GenerateTypeAndFromTypeAlias(*value.type_ctor);
+    GenerateTypeAndFromTypeAlias(value.type_ctor);
     GenerateObjectMember("value", value.value);
   });
 }
@@ -272,8 +275,7 @@
     // the primitive subtype, and therefore cannot use
     // GenerateTypeAndFromTypeAlias here.
     GenerateObjectMember("type", value.type->name);
-    if (value.subtype_ctor->from_type_alias)
-      GenerateExperimentalMaybeFromTypeAlias(*value.subtype_ctor);
+    GenerateExperimentalMaybeFromTypeAlias(flat::GetLayoutInvocation(value.subtype_ctor));
     GenerateObjectMember("members", value.members);
     GenerateObjectMember("strict", value.strictness == types::Strictness::kStrict);
     if (value.strictness == types::Strictness::kFlexible) {
@@ -327,9 +329,9 @@
   });
 }
 
-void JSONGenerator::GenerateTypeAndFromTypeAlias(const flat::TypeConstructorOld& value,
+void JSONGenerator::GenerateTypeAndFromTypeAlias(const flat::TypeConstructor& value,
                                                  Position position) {
-  GenerateTypeAndFromTypeAlias(TypeKind::kConcrete, value, position);
+  GenerateTypeAndFromTypeAlias(TypeKind::kConcrete, flat::GetTypeCtorAsPtr(value), position);
 }
 
 bool ShouldExposeTypeAliasOfParametrizedType(const flat::Type& type) {
@@ -340,39 +342,43 @@
 }
 
 void JSONGenerator::GenerateTypeAndFromTypeAlias(TypeKind parent_type_kind,
-                                                 const flat::TypeConstructorOld& value,
+                                                 flat::TypeConstructorPtr value,
                                                  Position position) {
-  if (fidl::ShouldExposeTypeAliasOfParametrizedType(*value.type)) {
-    if (value.from_type_alias) {
-      auto alias_decl = value.from_type_alias.value().decl;
-      auto type_alias = static_cast<const flat::TypeAlias*>(alias_decl);
-      GenerateParameterizedType(parent_type_kind, value.type, *type_alias->partial_type_ctor,
-                                position);
+  const auto* type = flat::GetType(value);
+  const auto& invocation = flat::GetLayoutInvocation(value);
+  if (fidl::ShouldExposeTypeAliasOfParametrizedType(*type)) {
+    const auto& invocation = flat::GetLayoutInvocation(value);
+    if (invocation.from_type_alias) {
+      GenerateParameterizedType(
+          parent_type_kind, type,
+          flat::GetTypeCtorAsPtr(invocation.from_type_alias->partial_type_ctor), position);
     } else {
-      GenerateParameterizedType(parent_type_kind, value.type, value, position);
+      GenerateParameterizedType(parent_type_kind, type, value, position);
     }
-    GenerateExperimentalMaybeFromTypeAlias(value);
+    GenerateExperimentalMaybeFromTypeAlias(invocation);
     return;
   }
 
   std::string key = parent_type_kind == TypeKind::kConcrete ? "type" : "element_type";
-  GenerateObjectMember(key, value.type, position);
-  GenerateExperimentalMaybeFromTypeAlias(value);
+  GenerateObjectMember(key, type, position);
+  GenerateExperimentalMaybeFromTypeAlias(invocation);
 }
 
-void JSONGenerator::GenerateExperimentalMaybeFromTypeAlias(const flat::TypeConstructorOld& value) {
-  if (value.from_type_alias)
-    GenerateObjectMember("experimental_maybe_from_type_alias", value.from_type_alias.value());
+void JSONGenerator::GenerateExperimentalMaybeFromTypeAlias(
+    const flat::LayoutInvocation& invocation) {
+  if (invocation.from_type_alias)
+    GenerateObjectMember("experimental_maybe_from_type_alias", invocation);
 }
 
 void JSONGenerator::GenerateParameterizedType(TypeKind parent_type_kind, const flat::Type* type,
-                                              const flat::TypeConstructorOld& type_ctor,
+                                              flat::TypeConstructorPtr type_ctor,
                                               Position position) {
+  const auto& invocation = flat::GetLayoutInvocation(type_ctor);
   std::string key = parent_type_kind == TypeKind::kConcrete ? "type" : "element_type";
 
   // Special case: type "bytes" is a builtin alias, so it will have no
   // user-specified arg type.
-  if (!type_ctor.maybe_arg_type_ctor) {
+  if (!flat::IsTypeConstructorDefined(invocation.element_type_raw)) {
     GenerateObjectMember(key, type, position);
     return;
   }
@@ -385,13 +391,13 @@
     switch (type->kind) {
       case flat::Type::Kind::kArray: {
         auto array_type = static_cast<const flat::ArrayType*>(type);
-        GenerateTypeAndFromTypeAlias(TypeKind::kParameterized, *type_ctor.maybe_arg_type_ctor);
+        GenerateTypeAndFromTypeAlias(TypeKind::kParameterized, invocation.element_type_raw);
         GenerateObjectMember("element_count", array_type->element_count->value);
         break;
       }
       case flat::Type::Kind::kVector: {
         auto vector_type = static_cast<const flat::VectorType*>(type);
-        GenerateTypeAndFromTypeAlias(TypeKind::kParameterized, *type_ctor.maybe_arg_type_ctor);
+        GenerateTypeAndFromTypeAlias(TypeKind::kParameterized, invocation.element_type_raw);
         if (*vector_type->element_count < flat::Size::Max())
           GenerateObjectMember("maybe_element_count", vector_type->element_count->value);
         GenerateObjectMember("nullable", vector_type->nullability);
@@ -400,8 +406,9 @@
       case flat::Type::Kind::kRequestHandle: {
         auto request_type = static_cast<const flat::RequestHandleType*>(type);
         GenerateObjectMember("subtype", request_type->protocol_type->name);
-        if (type_ctor.maybe_arg_type_ctor != nullptr) {
-          GenerateExperimentalMaybeFromTypeAlias(*type_ctor.maybe_arg_type_ctor);
+        if (flat::IsTypeConstructorDefined(invocation.element_type_raw)) {
+          GenerateExperimentalMaybeFromTypeAlias(
+              flat::GetLayoutInvocation(invocation.element_type_raw));
         }
         // TODO(fxbug.dev/43803): Add required and optional rights.
         GenerateObjectMember("nullable", request_type->nullability);
@@ -448,7 +455,7 @@
   GenerateObject([&]() {
     GenerateObjectMember("name", value.name, Position::kFirst);
     GenerateObjectMember("location", NameSpan(value.name));
-    GenerateTypeAndFromTypeAlias(*value.type_ctor);
+    GenerateTypeAndFromTypeAlias(value.type_ctor);
     if (value.attributes)
       GenerateObjectMember("maybe_attributes", value.attributes);
   });
@@ -460,7 +467,7 @@
     GenerateObjectMember("location", NameSpan(value.name));
     if (value.attributes)
       GenerateObjectMember("maybe_attributes", value.attributes);
-    GenerateTypeAndFromTypeAlias(*value.subtype_ctor);
+    GenerateTypeAndFromTypeAlias(value.subtype_ctor);
     GenerateObjectMember("properties", value.properties);
   });
 }
@@ -477,7 +484,7 @@
 
 void JSONGenerator::Generate(const flat::Service::Member& value) {
   GenerateObject([&]() {
-    GenerateTypeAndFromTypeAlias(*value.type_ctor, Position::kFirst);
+    GenerateTypeAndFromTypeAlias(value.type_ctor, Position::kFirst);
     GenerateObjectMember("name", value.name);
     GenerateObjectMember("location", NameSpan(value.name));
     if (value.attributes)
@@ -502,7 +509,7 @@
 
 void JSONGenerator::Generate(const flat::Struct::Member& value, bool is_request_or_response) {
   GenerateObject([&]() {
-    GenerateTypeAndFromTypeAlias(*value.type_ctor, Position::kFirst);
+    GenerateTypeAndFromTypeAlias(value.type_ctor, Position::kFirst);
     GenerateObjectMember("name", value.name);
     GenerateObjectMember("location", NameSpan(value.name));
     if (value.attributes)
@@ -532,7 +539,7 @@
     if (value.maybe_used) {
       assert(!value.span);
       GenerateObjectMember("reserved", false);
-      GenerateTypeAndFromTypeAlias(*value.maybe_used->type_ctor);
+      GenerateTypeAndFromTypeAlias(value.maybe_used->type_ctor);
       GenerateObjectMember("name", value.maybe_used->name);
       GenerateObjectMember("location", NameSpan(value.maybe_used->name));
       if (value.maybe_used->attributes)
@@ -585,7 +592,7 @@
       assert(!value.span);
       GenerateObjectMember("reserved", false);
       GenerateObjectMember("name", value.maybe_used->name);
-      GenerateTypeAndFromTypeAlias(*value.maybe_used->type_ctor);
+      GenerateTypeAndFromTypeAlias(value.maybe_used->type_ctor);
       GenerateObjectMember("location", NameSpan(value.maybe_used->name));
       if (value.maybe_used->attributes)
         GenerateObjectMember("maybe_attributes", value.maybe_used->attributes);
@@ -596,20 +603,20 @@
   });
 }
 
-void JSONGenerator::Generate(const flat::TypeConstructorOld::FromTypeAlias& value) {
+void JSONGenerator::Generate(const flat::LayoutInvocation& value) {
   GenerateObject([&]() {
-    GenerateObjectMember("name", value.decl->name, Position::kFirst);
+    GenerateObjectMember("name", value.from_type_alias->name, Position::kFirst);
     GenerateObjectPunctuation(Position::kSubsequent);
     EmitObjectKey("args");
 
     // In preparation of template support, it is better to expose a
-    // heterogenous argument list to backends, rather than the currently
+    // heterogeneous argument list to backends, rather than the currently
     // limited internal view.
     EmitArrayBegin();
-    if (value.maybe_arg_type) {
+    if (value.element_type_resolved) {
       Indent();
       EmitNewlineWithIndent();
-      Generate(value.maybe_arg_type->name);
+      Generate(flat::GetName(value.element_type_raw));
       Outdent();
       EmitNewlineWithIndent();
     }
@@ -617,36 +624,42 @@
 
     GenerateObjectMember("nullable", value.nullability);
 
-    if (value.maybe_size)
-      GenerateObjectMember("maybe_size", *value.maybe_size);
+    if (value.size_resolved)
+      GenerateObjectMember("maybe_size", *value.size_resolved);
   });
 }
 
-void JSONGenerator::Generate(const flat::TypeConstructorOld& value) {
+void JSONGenerator::Generate(const flat::TypeConstructor& value) {
+  GenerateTypeCtor(GetTypeCtorAsPtr(value));
+}
+
+void JSONGenerator::GenerateTypeCtor(const flat::TypeConstructorPtr& value) {
   GenerateObject([&]() {
-    GenerateObjectMember("name", value.type ? value.type->name : value.name, Position::kFirst);
+    GenerateObjectMember("name", GetType(value) ? GetType(value)->name : GetName(value),
+                         Position::kFirst);
     GenerateObjectPunctuation(Position::kSubsequent);
     EmitObjectKey("args");
+    const auto& invocation = flat::GetLayoutInvocation(value);
 
     // In preparation of template support, it is better to expose a
     // heterogenous argument list to backends, rather than the currently
     // limited internal view.
     EmitArrayBegin();
-    if (value.maybe_arg_type_ctor) {
+    if (invocation.element_type_resolved) {
       Indent();
       EmitNewlineWithIndent();
-      Generate(*value.maybe_arg_type_ctor);
+      GenerateTypeCtor(invocation.element_type_raw);
       Outdent();
       EmitNewlineWithIndent();
     }
     EmitArrayEnd();
 
-    GenerateObjectMember("nullable", value.nullability);
+    GenerateObjectMember("nullable", invocation.nullability);
 
-    if (value.maybe_size)
-      GenerateObjectMember("maybe_size", value.maybe_size);
-    if (value.handle_rights)
-      GenerateObjectMember("handle_rights", value.handle_rights);
+    if (invocation.size_raw)
+      GenerateObjectMember("maybe_size", *invocation.size_raw);
+    if (invocation.rights_raw)
+      GenerateObjectMember("handle_rights", *invocation.rights_raw);
   });
 }
 
@@ -656,7 +669,7 @@
     GenerateObjectMember("location", NameSpan(value.name));
     if (value.attributes)
       GenerateObjectMember("maybe_attributes", value.attributes);
-    GenerateObjectMember("partial_type_ctor", *value.partial_type_ctor);
+    GenerateObjectMember("partial_type_ctor", value.partial_type_ctor);
   });
 }
 
@@ -824,14 +837,14 @@
     for (const auto method_with_info : protocol->all_methods) {
       if (auto request = method_with_info.method->maybe_request) {
         for (const auto& member : request->members) {
-          if (auto dep_library = member.type_ctor->name.library()) {
+          if (auto dep_library = flat::GetName(member.type_ctor).library()) {
             add_dependency(dep_library);
           }
         }
       }
       if (auto response = method_with_info.method->maybe_response) {
         for (const auto& member : response->members) {
-          if (auto dep_library = member.type_ctor->name.library()) {
+          if (auto dep_library = flat::GetName(member.type_ctor).library()) {
             add_dependency(dep_library);
           }
         }
diff --git a/tools/fidl/fidlc/lib/new_syntax_converter.cc b/tools/fidl/fidlc/lib/new_syntax_converter.cc
index d67e1ac..6d70e5f 100644
--- a/tools/fidl/fidlc/lib/new_syntax_converter.cc
+++ b/tools/fidl/fidlc/lib/new_syntax_converter.cc
@@ -59,11 +59,11 @@
 
   auto type_alias_ptr = static_cast<const flat::TypeAlias*>(decl_ptr);
   auto underlying_type =
-      resolve_as_user_defined_type(type_alias_ptr->partial_type_ctor->name, true);
+      resolve_as_user_defined_type(flat::GetName(type_alias_ptr->partial_type_ctor), true);
   if (underlying_type != std::nullopt) {
     return underlying_type;
   }
-  return UnderlyingType(type_alias_ptr->partial_type_ctor->type->kind, true);
+  return UnderlyingType(flat::GetType(type_alias_ptr->partial_type_ctor)->kind, true);
 }
 
 // Matches a string keyword to the "builtin" representing the FIDL-native type
diff --git a/tools/fidl/fidlc/lib/type_shape.cc b/tools/fidl/fidlc/lib/type_shape.cc
index 6a1a08cb..b2bb539 100644
--- a/tools/fidl/fidlc/lib/type_shape.cc
+++ b/tools/fidl/fidlc/lib/type_shape.cc
@@ -164,11 +164,11 @@
   std::any Visit(const flat::RequestHandleType& object) override { return DataSize(kHandleSize); }
 
   std::any Visit(const flat::Enum& object) override {
-    return UnalignedSize(object.subtype_ctor->type);
+    return UnalignedSize(GetType(object.subtype_ctor));
   }
 
   std::any Visit(const flat::Bits& object) override {
-    return UnalignedSize(object.subtype_ctor->type);
+    return UnalignedSize(GetType(object.subtype_ctor));
   }
 
   std::any Visit(const flat::Service& object) override { return DataSize(kHandleSize); }
@@ -198,7 +198,7 @@
   }
 
   std::any Visit(const flat::Struct::Member& object) override {
-    return UnalignedSize(object.type_ctor->type);
+    return UnalignedSize(flat::GetType(object.type_ctor));
   }
 
   std::any Visit(const flat::Table& object) override { return DataSize(16); }
@@ -208,7 +208,7 @@
   }
 
   std::any Visit(const flat::Table::Member::Used& object) override {
-    return UnalignedSize(object.type_ctor->type);
+    return UnalignedSize(GetType(object.type_ctor));
   }
 
   std::any Visit(const flat::Union& object) override { return DataSize(24); }
@@ -218,7 +218,7 @@
   }
 
   std::any Visit(const flat::Union::Member::Used& object) override {
-    return UnalignedSize(object.type_ctor->type);
+    return UnalignedSize(GetType(object.type_ctor));
   }
 
   std::any Visit(const flat::Protocol& object) override { return DataSize(kHandleSize); }
@@ -271,9 +271,13 @@
 
   std::any Visit(const flat::RequestHandleType& object) override { return DataSize(kHandleSize); }
 
-  std::any Visit(const flat::Enum& object) override { return Alignment(object.subtype_ctor->type); }
+  std::any Visit(const flat::Enum& object) override {
+    return Alignment(GetType(object.subtype_ctor));
+  }
 
-  std::any Visit(const flat::Bits& object) override { return Alignment(object.subtype_ctor->type); }
+  std::any Visit(const flat::Bits& object) override {
+    return Alignment(GetType(object.subtype_ctor));
+  }
 
   std::any Visit(const flat::Service& object) override { return DataSize(kHandleSize); }
 
@@ -308,7 +312,7 @@
   }
 
   std::any Visit(const flat::Struct::Member& object) override {
-    return Alignment(object.type_ctor->type);
+    return Alignment(flat::GetType(object.type_ctor));
   }
 
   std::any Visit(const flat::Table& object) override { return DataSize(8); }
@@ -318,7 +322,7 @@
   }
 
   std::any Visit(const flat::Table::Member::Used& object) override {
-    return Alignment(object.type_ctor->type);
+    return Alignment(GetType(object.type_ctor));
   }
 
   std::any Visit(const flat::Union& object) override { return DataSize(8); }
@@ -328,7 +332,7 @@
   }
 
   std::any Visit(const flat::UnionMember::Used& object) override {
-    return Alignment(object.type_ctor->type);
+    return Alignment(GetType(object.type_ctor));
   }
 
   std::any Visit(const flat::Protocol& object) override { return DataSize(kHandleSize); }
@@ -402,9 +406,9 @@
 
   std::any Visit(const flat::RequestHandleType& object) override { return DataSize(0); }
 
-  std::any Visit(const flat::Enum& object) override { return Depth(object.subtype_ctor->type); }
+  std::any Visit(const flat::Enum& object) override { return Depth(GetType(object.subtype_ctor)); }
 
-  std::any Visit(const flat::Bits& object) override { return Depth(object.subtype_ctor->type); }
+  std::any Visit(const flat::Bits& object) override { return Depth(GetType(object.subtype_ctor)); }
 
   std::any Visit(const flat::Service& object) override { return DataSize(0); }
 
@@ -423,7 +427,7 @@
   }
 
   std::any Visit(const flat::Struct::Member& object) override {
-    return Depth(object.type_ctor->type);
+    return Depth(flat::GetType(object.type_ctor));
   }
 
   std::any Visit(const flat::Table& object) override {
@@ -441,7 +445,7 @@
   }
 
   std::any Visit(const flat::Table::Member::Used& object) override {
-    return DataSize(1) + Depth(object.type_ctor->type);
+    return DataSize(1) + Depth(GetType(object.type_ctor));
   }
 
   std::any Visit(const flat::Union& object) override {
@@ -459,7 +463,7 @@
   }
 
   std::any Visit(const flat::Union::Member::Used& object) override {
-    return Depth(object.type_ctor->type);
+    return Depth(GetType(object.type_ctor));
   }
 
   std::any Visit(const flat::Protocol& object) override { return DataSize(0); }
@@ -537,11 +541,11 @@
   std::any Visit(const flat::RequestHandleType& object) override { return DataSize(1); }
 
   std::any Visit(const flat::Enum& object) override {
-    return MaxHandles(object.subtype_ctor->type);
+    return MaxHandles(GetType(object.subtype_ctor));
   }
 
   std::any Visit(const flat::Bits& object) override {
-    return MaxHandles(object.subtype_ctor->type);
+    return MaxHandles(GetType(object.subtype_ctor));
   }
 
   std::any Visit(const flat::Service& object) override { return DataSize(1); }
@@ -553,7 +557,7 @@
     // all current tests and Fuchsia compilation, so fixing it isn't super-urgent.
     if (object.recursive) {
       for (const auto& member : object.members) {
-        switch (member.type_ctor->type->kind) {
+        switch (flat::GetType(member.type_ctor)->kind) {
           case flat::Type::Kind::kHandle:
           case flat::Type::Kind::kRequestHandle:
             return std::numeric_limits<DataSize>::max();
@@ -579,7 +583,7 @@
   }
 
   std::any Visit(const flat::Struct::Member& object) override {
-    return MaxHandles(object.type_ctor->type);
+    return MaxHandles(flat::GetType(object.type_ctor));
   }
 
   std::any Visit(const flat::Table& object) override {
@@ -597,7 +601,7 @@
   }
 
   std::any Visit(const flat::Table::Member::Used& object) override {
-    return MaxHandles(object.type_ctor->type);
+    return MaxHandles(GetType(object.type_ctor));
   }
 
   std::any Visit(const flat::Union& object) override {
@@ -615,7 +619,7 @@
   }
 
   std::any Visit(const flat::Union::Member::Used& object) override {
-    return MaxHandles(object.type_ctor->type);
+    return MaxHandles(GetType(object.type_ctor));
   }
 
   std::any Visit(const flat::Protocol& object) override { return DataSize(1); }
@@ -678,11 +682,11 @@
   std::any Visit(const flat::RequestHandleType& object) override { return DataSize(0); }
 
   std::any Visit(const flat::Enum& object) override {
-    return MaxOutOfLine(object.subtype_ctor->type);
+    return MaxOutOfLine(GetType(object.subtype_ctor));
   }
 
   std::any Visit(const flat::Bits& object) override {
-    return MaxOutOfLine(object.subtype_ctor->type);
+    return MaxOutOfLine(GetType(object.subtype_ctor));
   }
 
   std::any Visit(const flat::Service& object) override { return DataSize(0); }
@@ -698,7 +702,7 @@
   }
 
   std::any Visit(const flat::Struct::Member& object) override {
-    return MaxOutOfLine(object.type_ctor->type);
+    return MaxOutOfLine(flat::GetType(object.type_ctor));
   }
 
   std::any Visit(const flat::Table& object) override {
@@ -733,7 +737,7 @@
   }
 
   std::any Visit(const flat::Table::Member::Used& object) override {
-    return ObjectAlign(MaxOutOfLine(object.type_ctor->type));
+    return ObjectAlign(MaxOutOfLine(GetType(object.type_ctor)));
   }
 
   std::any Visit(const flat::Union& object) override {
@@ -753,7 +757,7 @@
   }
 
   std::any Visit(const flat::Union::Member::Used& object) override {
-    return MaxOutOfLine(object.type_ctor->type);
+    return MaxOutOfLine(GetType(object.type_ctor));
   }
 
   std::any Visit(const flat::Protocol& object) override { return DataSize(0); }
@@ -827,11 +831,11 @@
   std::any Visit(const flat::RequestHandleType& object) override { return false; }
 
   std::any Visit(const flat::Enum& object) override {
-    return HasPadding(object.subtype_ctor->type);
+    return HasPadding(GetType(object.subtype_ctor));
   }
 
   std::any Visit(const flat::Bits& object) override {
-    return HasPadding(object.subtype_ctor->type);
+    return HasPadding(GetType(object.subtype_ctor));
   }
 
   std::any Visit(const flat::Service& object) override { return false; }
@@ -847,7 +851,8 @@
   }
 
   std::any Visit(const flat::Struct::Member& object) override {
-    return object.fieldshape(wire_format()).Padding() > 0 || HasPadding(object.type_ctor->type);
+    return object.fieldshape(wire_format()).Padding() > 0 ||
+           HasPadding(flat::GetType(object.type_ctor));
   }
 
   std::any Visit(const flat::Table& object) override {
@@ -865,8 +870,8 @@
   }
 
   std::any Visit(const flat::Table::Member::Used& object) override {
-    return Padding(UnalignedSize(object.type_ctor->type, wire_format()), 8) > 0 ||
-           HasPadding(object.type_ctor->type) || object.fieldshape(wire_format()).Padding() > 0;
+    return Padding(UnalignedSize(GetType(object.type_ctor), wire_format()), 8) > 0 ||
+           HasPadding(GetType(object.type_ctor)) || object.fieldshape(wire_format()).Padding() > 0;
   }
 
   std::any Visit(const flat::Union& object) override {
@@ -925,11 +930,11 @@
   std::any Visit(const flat::RequestHandleType& object) override { return false; }
 
   std::any Visit(const flat::Enum& object) override {
-    return HasFlexibleEnvelope(object.subtype_ctor->type, wire_format());
+    return HasFlexibleEnvelope(GetType(object.subtype_ctor), wire_format());
   }
 
   std::any Visit(const flat::Bits& object) override {
-    return HasFlexibleEnvelope(object.subtype_ctor->type, wire_format());
+    return HasFlexibleEnvelope(GetType(object.subtype_ctor), wire_format());
   }
 
   std::any Visit(const flat::Service& object) override { return false; }
@@ -945,7 +950,7 @@
   }
 
   std::any Visit(const flat::Struct::Member& object) override {
-    return HasFlexibleEnvelope(object.type_ctor->type, wire_format());
+    return HasFlexibleEnvelope(flat::GetType(object.type_ctor), wire_format());
   }
 
   std::any Visit(const flat::Table& object) override {
@@ -967,7 +972,7 @@
   }
 
   std::any Visit(const flat::Table::Member::Used& object) override {
-    return HasFlexibleEnvelope(object.type_ctor->type, wire_format());
+    return HasFlexibleEnvelope(GetType(object.type_ctor), wire_format());
   }
 
   std::any Visit(const flat::Union& object) override {
@@ -989,7 +994,7 @@
   }
 
   std::any Visit(const flat::Union::Member::Used& object) override {
-    return HasFlexibleEnvelope(object.type_ctor->type, wire_format());
+    return HasFlexibleEnvelope(GetType(object.type_ctor), wire_format());
   }
 
   std::any Visit(const flat::Protocol& object) override { return false; }
diff --git a/zircon/system/utest/fidl-compiler/alias_tests.cc b/zircon/system/utest/fidl-compiler/alias_tests.cc
index 9f7f03b..2e1d157 100644
--- a/zircon/system/utest/fidl-compiler/alias_tests.cc
+++ b/zircon/system/utest/fidl-compiler/alias_tests.cc
@@ -12,6 +12,9 @@
 
 namespace {
 
+using fidl::flat::GetLayoutInvocation;
+using fidl::flat::GetType;
+
 TEST(AliasTests, BadDuplicateAlias) {
   fidl::ExperimentalFlags experimental_flags;
   experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
@@ -58,19 +61,20 @@
   ASSERT_NOT_NULL(msg);
   ASSERT_EQ(msg->members.size(), 1);
 
-  auto type = msg->members[0].type_ctor->type;
+  auto type = GetType(msg->members[0].type_ctor);
   ASSERT_EQ(type->kind, fidl::flat::Type::Kind::kPrimitive);
   ASSERT_EQ(type->nullability, fidl::types::Nullability::kNonnullable);
-  ASSERT_TRUE(msg->members[0].type_ctor->from_type_alias);
 
   auto primitive_type = static_cast<const fidl::flat::PrimitiveType*>(type);
   ASSERT_EQ(primitive_type->subtype, fidl::types::PrimitiveSubtype::kInt16);
 
-  auto from_type_alias = msg->members[0].type_ctor->from_type_alias.value();
-  EXPECT_STR_EQ(fidl::NameFlatName(from_type_alias.decl->name).c_str(), "example/alias_of_int16");
-  EXPECT_NULL(from_type_alias.maybe_arg_type);
-  EXPECT_NULL(from_type_alias.maybe_size);
-  EXPECT_EQ(from_type_alias.nullability, fidl::types::Nullability::kNonnullable);
+  auto invocation = GetLayoutInvocation(msg->members[0].type_ctor);
+  ASSERT_NOT_NULL(invocation.from_type_alias);
+  EXPECT_STR_EQ(fidl::NameFlatName(invocation.from_type_alias->name).c_str(),
+                "example/alias_of_int16");
+  EXPECT_NULL(invocation.element_type_resolved);
+  EXPECT_NULL(invocation.size_resolved);
+  EXPECT_EQ(invocation.nullability, fidl::types::Nullability::kNonnullable);
 }
 
 TEST(AliasTests, GoodPrimitiveTypeAliasBeforeUse) {
@@ -88,18 +92,20 @@
   ASSERT_NOT_NULL(msg);
   ASSERT_EQ(msg->members.size(), 1);
 
-  auto type = msg->members[0].type_ctor->type;
+  auto type = GetType(msg->members[0].type_ctor);
   ASSERT_EQ(type->kind, fidl::flat::Type::Kind::kPrimitive);
   ASSERT_EQ(type->nullability, fidl::types::Nullability::kNonnullable);
 
   auto primitive_type = static_cast<const fidl::flat::PrimitiveType*>(type);
   ASSERT_EQ(primitive_type->subtype, fidl::types::PrimitiveSubtype::kInt16);
 
-  auto from_type_alias = msg->members[0].type_ctor->from_type_alias.value();
-  EXPECT_STR_EQ(fidl::NameFlatName(from_type_alias.decl->name).c_str(), "example/alias_of_int16");
-  EXPECT_NULL(from_type_alias.maybe_arg_type);
-  EXPECT_NULL(from_type_alias.maybe_size);
-  EXPECT_EQ(from_type_alias.nullability, fidl::types::Nullability::kNonnullable);
+  auto invocation = GetLayoutInvocation(msg->members[0].type_ctor);
+  ASSERT_NOT_NULL(invocation.from_type_alias);
+  EXPECT_STR_EQ(fidl::NameFlatName(invocation.from_type_alias->name).c_str(),
+                "example/alias_of_int16");
+  EXPECT_NULL(invocation.element_type_resolved);
+  EXPECT_NULL(invocation.size_resolved);
+  EXPECT_EQ(invocation.nullability, fidl::types::Nullability::kNonnullable);
 }
 
 TEST(AliasTests, BadPrimitiveTypeShadowing) {
@@ -144,7 +150,6 @@
 )FIDL",
                       experimental_flags);
   ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrCannotBeNullable);
-  ASSERT_SUBSTR(library.errors()[0]->msg.c_str(), "int64");
 }
 
 TEST(AliasTests, BadNoOptionalOnPrimitiveOld) {
@@ -160,6 +165,21 @@
   ASSERT_SUBSTR(library.errors()[0]->msg.c_str(), "int64");
 }
 
+TEST(AliasTests, BadMultipleConstraintsOnPrimitive) {
+  fidl::ExperimentalFlags experimental_flags;
+  experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
+  TestLibrary library(R"FIDL(
+library test.optionals;
+
+type Bad = struct {
+    opt_num int64:<optional, foo, bar>;
+};
+
+)FIDL",
+                      experimental_flags);
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrTooManyConstraints);
+}
+
 TEST(AliasTests, BadNoOptionalOnAliasedPrimitive) {
   fidl::ExperimentalFlags experimental_flags;
   experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
@@ -175,7 +195,6 @@
 )FIDL",
                       experimental_flags);
   ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrCannotBeNullable);
-  ASSERT_SUBSTR(library.errors()[0]->msg.c_str(), "int64");
 }
 
 TEST(AliasTests, BadNoOptionalOnAliasedPrimitiveOld) {
@@ -190,7 +209,7 @@
 
 )FIDL");
   ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrCannotBeNullable);
-  ASSERT_SUBSTR(library.errors()[0]->msg.c_str(), "int64");
+  ASSERT_SUBSTR(library.errors()[0]->msg.c_str(), "test.optionals/alias");
 }
 
 TEST(AliasTests, GoodVectorParameterizedOnDecl) {
@@ -208,7 +227,7 @@
   ASSERT_NOT_NULL(msg);
   ASSERT_EQ(msg->members.size(), 1);
 
-  auto type = msg->members[0].type_ctor->type;
+  auto type = GetType(msg->members[0].type_ctor);
   ASSERT_EQ(type->kind, fidl::flat::Type::Kind::kVector);
   ASSERT_EQ(type->nullability, fidl::types::Nullability::kNonnullable);
 
@@ -217,12 +236,13 @@
   ASSERT_EQ(static_cast<uint32_t>(*vector_type->element_count),
             static_cast<uint32_t>(fidl::flat::Size::Max()));
 
-  auto from_type_alias = msg->members[0].type_ctor->from_type_alias.value();
-  EXPECT_STR_EQ(fidl::NameFlatName(from_type_alias.decl->name).c_str(),
+  auto invocation = GetLayoutInvocation(msg->members[0].type_ctor);
+  ASSERT_NOT_NULL(invocation.from_type_alias);
+  EXPECT_STR_EQ(fidl::NameFlatName(invocation.from_type_alias->name).c_str(),
                 "example/alias_of_vector_of_string");
-  EXPECT_NULL(from_type_alias.maybe_arg_type);
-  EXPECT_NULL(from_type_alias.maybe_size);
-  EXPECT_EQ(from_type_alias.nullability, fidl::types::Nullability::kNonnullable);
+  EXPECT_NULL(invocation.element_type_resolved);
+  EXPECT_NULL(invocation.size_resolved);
+  EXPECT_EQ(invocation.nullability, fidl::types::Nullability::kNonnullable);
 }
 
 TEST(AliasTests, BadVectorParameterizedOnUse) {
@@ -238,7 +258,8 @@
 alias alias_of_vector = vector;
 )FIDL",
                       experimental_flags);
-  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrMustBeParameterized);
+  // NOTE(fxbug.dev/72924): A more general error is thrown in the new syntax
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrWrongNumberOfLayoutParameters);
 }
 
 TEST(AliasTests, BadVectorParameterizedOnUseOld) {
@@ -267,7 +288,8 @@
 alias alias_of_vector_max_8 = vector:8;
 )FIDL",
                       experimental_flags);
-  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrMustBeParameterized);
+  // NOTE(fxbug.dev/72924): A more general error is thrown in the new syntax
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrWrongNumberOfLayoutParameters);
 }
 
 TEST(AliasTests, BadVectorBoundedOnDeclOld) {
@@ -298,7 +320,7 @@
   ASSERT_NOT_NULL(msg);
   ASSERT_EQ(msg->members.size(), 1);
 
-  auto type = msg->members[0].type_ctor->type;
+  auto type = GetType(msg->members[0].type_ctor);
   ASSERT_EQ(type->kind, fidl::flat::Type::Kind::kVector);
   ASSERT_EQ(type->nullability, fidl::types::Nullability::kNonnullable);
 
@@ -306,13 +328,14 @@
   ASSERT_EQ(vector_type->element_type->kind, fidl::flat::Type::Kind::kString);
   ASSERT_EQ(static_cast<uint32_t>(*vector_type->element_count), 8u);
 
-  auto from_type_alias = msg->members[0].type_ctor->from_type_alias.value();
-  EXPECT_STR_EQ(fidl::NameFlatName(from_type_alias.decl->name).c_str(),
+  auto invocation = GetLayoutInvocation(msg->members[0].type_ctor);
+  ASSERT_NOT_NULL(invocation.from_type_alias);
+  EXPECT_STR_EQ(fidl::NameFlatName(invocation.from_type_alias->name).c_str(),
                 "example/alias_of_vector_of_string");
-  EXPECT_NULL(from_type_alias.maybe_arg_type);
-  EXPECT_NOT_NULL(from_type_alias.maybe_size);
-  EXPECT_EQ(static_cast<uint32_t>(*from_type_alias.maybe_size), 8u);
-  EXPECT_EQ(from_type_alias.nullability, fidl::types::Nullability::kNonnullable);
+  EXPECT_NULL(invocation.element_type_resolved);
+  EXPECT_NOT_NULL(invocation.size_resolved);
+  EXPECT_EQ(static_cast<uint32_t>(*invocation.size_resolved), 8u);
+  EXPECT_EQ(invocation.nullability, fidl::types::Nullability::kNonnullable);
 }
 
 TEST(AliasTests, GoodVectorNullableOnDecl) {
@@ -330,7 +353,7 @@
   ASSERT_NOT_NULL(msg);
   ASSERT_EQ(msg->members.size(), 1);
 
-  auto type = msg->members[0].type_ctor->type;
+  auto type = GetType(msg->members[0].type_ctor);
   ASSERT_EQ(type->kind, fidl::flat::Type::Kind::kVector);
   ASSERT_EQ(type->nullability, fidl::types::Nullability::kNullable);
 
@@ -339,12 +362,13 @@
   ASSERT_EQ(static_cast<uint32_t>(*vector_type->element_count),
             static_cast<uint32_t>(fidl::flat::Size::Max()));
 
-  auto from_type_alias = msg->members[0].type_ctor->from_type_alias.value();
-  EXPECT_STR_EQ(fidl::NameFlatName(from_type_alias.decl->name).c_str(),
+  auto invocation = GetLayoutInvocation(msg->members[0].type_ctor);
+  ASSERT_NOT_NULL(invocation.from_type_alias);
+  EXPECT_STR_EQ(fidl::NameFlatName(invocation.from_type_alias->name).c_str(),
                 "example/alias_of_vector_of_string_nullable");
-  EXPECT_NULL(from_type_alias.maybe_arg_type);
-  EXPECT_NULL(from_type_alias.maybe_size);
-  EXPECT_EQ(from_type_alias.nullability, fidl::types::Nullability::kNonnullable);
+  EXPECT_NULL(invocation.element_type_resolved);
+  EXPECT_NULL(invocation.size_resolved);
+  EXPECT_EQ(invocation.nullability, fidl::types::Nullability::kNonnullable);
 }
 
 TEST(AliasTests, GoodVectorNullableOnUse) {
@@ -362,7 +386,7 @@
   ASSERT_NOT_NULL(msg);
   ASSERT_EQ(msg->members.size(), 1);
 
-  auto type = msg->members[0].type_ctor->type;
+  auto type = GetType(msg->members[0].type_ctor);
   ASSERT_EQ(type->kind, fidl::flat::Type::Kind::kVector);
   ASSERT_EQ(type->nullability, fidl::types::Nullability::kNullable);
 
@@ -371,12 +395,13 @@
   ASSERT_EQ(static_cast<uint32_t>(*vector_type->element_count),
             static_cast<uint32_t>(fidl::flat::Size::Max()));
 
-  auto from_type_alias = msg->members[0].type_ctor->from_type_alias.value();
-  EXPECT_STR_EQ(fidl::NameFlatName(from_type_alias.decl->name).c_str(),
+  auto invocation = GetLayoutInvocation(msg->members[0].type_ctor);
+  ASSERT_NOT_NULL(invocation.from_type_alias);
+  EXPECT_STR_EQ(fidl::NameFlatName(invocation.from_type_alias->name).c_str(),
                 "example/alias_of_vector_of_string");
-  EXPECT_NULL(from_type_alias.maybe_arg_type);
-  EXPECT_NULL(from_type_alias.maybe_size);
-  EXPECT_EQ(from_type_alias.nullability, fidl::types::Nullability::kNullable);
+  EXPECT_NULL(invocation.element_type_resolved);
+  EXPECT_NULL(invocation.size_resolved);
+  EXPECT_EQ(invocation.nullability, fidl::types::Nullability::kNullable);
 }
 
 TEST(AliasTests, BadCannotParameterizeTwice) {
@@ -392,7 +417,8 @@
 alias alias_of_vector_of_string = vector<string>;
 )FIDL",
                       experimental_flags);
-  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrCannotParameterizeAlias);
+  // NOTE(fxbug.dev/72924): A more general error is thrown in the new syntax
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrWrongNumberOfLayoutParameters);
 }
 
 TEST(AliasTests, BadCannotParameterizeTwiceOld) {
@@ -636,4 +662,36 @@
   ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrCannotHaveSize)
 }
 
+TEST(AliasTests, BadBoundsOnRequestType) {
+  // Test that CreateInvocation::Size doesn't get ignored on type aliases when
+  // applying constraints
+  TestLibrary library(R"FIDL(
+library example;
+
+protocol Foo {};
+
+alias MyRequest = request<Foo>;
+
+struct Data {
+  MyRequest:10 foo;
+};
+)FIDL");
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrCannotHaveSize);
+}
+
+TEST(AliasTests, BadBoundsOnArray) {
+  // Test that CreateInvocation::Size doesn't get ignored on type aliases when
+  // applying constraints
+  TestLibrary library(R"FIDL(
+library example;
+
+alias MyArray = array<uint8>:10;
+
+struct Data {
+  MyArray:10 foo;
+};
+)FIDL");
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrCannotParameterizeAlias);
+}
+
 }  // namespace
diff --git a/zircon/system/utest/fidl-compiler/array_tests.cc b/zircon/system/utest/fidl-compiler/array_tests.cc
index b5a4bf7..f98dfa0 100644
--- a/zircon/system/utest/fidl-compiler/array_tests.cc
+++ b/zircon/system/utest/fidl-compiler/array_tests.cc
@@ -69,7 +69,8 @@
 };
 )FIDL",
                       experimental_flags);
-  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrMustHaveSize);
+  // NOTE(fxbug.dev/72924): A more general error is thrown in the new syntax
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrWrongNumberOfLayoutParameters);
 }
 
 TEST(ArrayTests, BadNonParameterizedArrayOld) {
@@ -94,7 +95,47 @@
 };
 )FIDL",
                       experimental_flags);
-  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrMustBeParameterized);
+  // NOTE(fxbug.dev/72924): A more general error is thrown in the new syntax
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrWrongNumberOfLayoutParameters);
+}
+
+TEST(ArrayTests, BadOptionalArrayOld) {
+  TestLibrary library(R"FIDL(
+library example;
+
+struct S {
+    array<uint8>:10? arr;
+};
+)FIDL");
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrCannotBeNullable);
+}
+
+TEST(ArrayTests, BadOptionalArray) {
+  fidl::ExperimentalFlags experimental_flags;
+  experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
+  TestLibrary library(R"FIDL(
+library example;
+
+type S = struct {
+    arr array<uint8, 10>:optional;
+};
+)FIDL",
+                      experimental_flags);
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrCannotBeNullable);
+}
+
+TEST(ArrayTest, BadMultipleConstraintsOnArray) {
+  fidl::ExperimentalFlags experimental_flags;
+  experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
+  TestLibrary library(R"FIDL(
+library example;
+
+type S = struct {
+    arr array<uint8, 10>:<optional, foo, bar>;
+};
+)FIDL",
+                      experimental_flags);
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrTooManyConstraints);
 }
 
 }  // namespace
diff --git a/zircon/system/utest/fidl-compiler/bits_tests.cc b/zircon/system/utest/fidl-compiler/bits_tests.cc
index f623add..1937563 100644
--- a/zircon/system/utest/fidl-compiler/bits_tests.cc
+++ b/zircon/system/utest/fidl-compiler/bits_tests.cc
@@ -288,7 +288,7 @@
   EXPECT_EQ(bits->mask, 42);
 }
 
-TEST(EnumsTests, BadBitsShantBeNullable) {
+TEST(BitsTests, BadBitsShantBeNullable) {
   fidl::ExperimentalFlags experimental_flags;
   experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
   TestLibrary library(R"FIDL(
@@ -304,10 +304,9 @@
 )FIDL",
                       experimental_flags);
   ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrCannotBeNullable);
-  ASSERT_SUBSTR(library.errors()[0]->msg.c_str(), "NotNullable");
 }
 
-TEST(EnumsTests, BadBitsShantBeNullableOld) {
+TEST(BitsTests, BadBitsShantBeNullableOld) {
   TestLibrary library(R"FIDL(
 library example;
 
@@ -323,4 +322,22 @@
   ASSERT_SUBSTR(library.errors()[0]->msg.c_str(), "NotNullable");
 }
 
+TEST(BitsTests, BadBitsMultipleConstraints) {
+  fidl::ExperimentalFlags experimental_flags;
+  experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
+  TestLibrary library(R"FIDL(
+library example;
+
+type NotNullable = bits {
+    MEMBER = 1;
+};
+
+type Struct = struct {
+    not_nullable NotNullable:<optional, foo, bar>;
+};
+)FIDL",
+                      experimental_flags);
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrTooManyConstraints);
+}
+
 }  // namespace
diff --git a/zircon/system/utest/fidl-compiler/canonical_names_tests.cc b/zircon/system/utest/fidl-compiler/canonical_names_tests.cc
index d574240..660e058 100644
--- a/zircon/system/utest/fidl-compiler/canonical_names_tests.cc
+++ b/zircon/system/utest/fidl-compiler/canonical_names_tests.cc
@@ -9,6 +9,7 @@
 
 #include "error_test.h"
 #include "fidl/diagnostics.h"
+#include "fidl/experimental_flags.h"
 #include "test_library.h"
 
 namespace {
diff --git a/zircon/system/utest/fidl-compiler/coded_types_generator_tests.cc b/zircon/system/utest/fidl-compiler/coded_types_generator_tests.cc
index 1f04d56..7599d0d 100644
--- a/zircon/system/utest/fidl-compiler/coded_types_generator_tests.cc
+++ b/zircon/system/utest/fidl-compiler/coded_types_generator_tests.cc
@@ -176,8 +176,8 @@
   ASSERT_NOT_NULL(decl);
   const fidl::flat::Struct* str = static_cast<fidl::flat::Struct*>(decl);
   auto elem_might_mutate = [&str](size_t index) {
-    const fidl::flat::VectorType* vec =
-        static_cast<const fidl::flat::VectorType*>(str->members.at(index).type_ctor->type);
+    const fidl::flat::VectorType* vec = static_cast<const fidl::flat::VectorType*>(
+        fidl::flat::GetType(str->members.at(index).type_ctor));
     return fidl::ComputeMemcpyCompatibility(vec->element_type);
   };
   // Note: these EXPECT_EQ are not in a loop so that they give more useful errors.
diff --git a/zircon/system/utest/fidl-compiler/consts_tests.cc b/zircon/system/utest/fidl-compiler/consts_tests.cc
index 9d14d59..43a0ab9 100644
--- a/zircon/system/utest/fidl-compiler/consts_tests.cc
+++ b/zircon/system/utest/fidl-compiler/consts_tests.cc
@@ -7,6 +7,7 @@
 #include <zxtest/zxtest.h>
 
 #include "error_test.h"
+#include "fidl/diagnostics.h"
 #include "test_library.h"
 
 namespace {
@@ -354,18 +355,18 @@
   ASSERT_COMPILED_AND_CONVERT(library);
 
   auto inferred_const = library.LookupConstant("INFERRED");
-  ASSERT_NOT_NULL(inferred_const->type_ctor->type);
-  ASSERT_EQ(inferred_const->type_ctor->type->kind, fidl::flat::Type::Kind::kString);
-  auto inferred_string_type =
-      static_cast<const fidl::flat::StringType*>(inferred_const->type_ctor->type);
+  auto inferred_const_type = fidl::flat::GetType(inferred_const->type_ctor);
+  ASSERT_NOT_NULL(inferred_const_type);
+  ASSERT_EQ(inferred_const_type->kind, fidl::flat::Type::Kind::kString);
+  auto inferred_string_type = static_cast<const fidl::flat::StringType*>(inferred_const_type);
   ASSERT_NOT_NULL(inferred_string_type->max_size);
   ASSERT_EQ(static_cast<uint32_t>(*inferred_string_type->max_size), 4294967295u);
 
   auto explicit_const = library.LookupConstant("EXPLICIT");
-  ASSERT_NOT_NULL(explicit_const->type_ctor->type);
-  ASSERT_EQ(explicit_const->type_ctor->type->kind, fidl::flat::Type::Kind::kString);
-  auto explicit_string_type =
-      static_cast<const fidl::flat::StringType*>(explicit_const->type_ctor->type);
+  auto explicit_const_type = fidl::flat::GetType(explicit_const->type_ctor);
+  ASSERT_NOT_NULL(explicit_const_type);
+  ASSERT_EQ(explicit_const_type->kind, fidl::flat::Type::Kind::kString);
+  auto explicit_string_type = static_cast<const fidl::flat::StringType*>(explicit_const_type);
   ASSERT_NOT_NULL(explicit_string_type->max_size);
   ASSERT_EQ(static_cast<uint32_t>(*explicit_string_type->max_size), 4u);
 }
@@ -825,7 +826,9 @@
 )FIDL",
                       &shared, experimental_flags);
   ASSERT_TRUE(library.AddDependentLibrary(std::move(converted_dependency)));
-  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrCouldNotParseSizeBound);
+  // NOTE(fxbug.dev/72924): we provide a more general error because there are multiple
+  // possible interpretations.
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrUnexpectedConstraint);
 }
 
 TEST(ConstsTests, BadMaxBoundTestLibraryQualifiedWithOldDep) {
@@ -849,7 +852,9 @@
 )FIDL",
                       &shared, experimental_flags);
   ASSERT_TRUE(library.AddDependentLibrary(std::move(dependency)));
-  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrCouldNotParseSizeBound);
+  // NOTE(fxbug.dev/72924): we provide a more general error because there are multiple
+  // possible interpretations.
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrUnexpectedConstraint);
 }
 
 TEST(ConstsTests, BadMaxBoundTestLibraryQualifiedOld) {
@@ -882,7 +887,8 @@
 const u uint8<string> = 0;
 )FIDL",
                       experimental_flags);
-  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrCannotBeParameterized);
+  // NOTE(fxbug.dev/72924): we provide a more general error in the new syntax
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrWrongNumberOfLayoutParameters);
 }
 
 TEST(ConstsTests, BadParameterizePrimitiveOld) {
diff --git a/zircon/system/utest/fidl-compiler/declaration_order_tests.cc b/zircon/system/utest/fidl-compiler/declaration_order_tests.cc
index 47d5df3..142b6f5 100644
--- a/zircon/system/utest/fidl-compiler/declaration_order_tests.cc
+++ b/zircon/system/utest/fidl-compiler/declaration_order_tests.cc
@@ -97,7 +97,7 @@
 
 )FIDL");
     TestLibrary library(source);
-    ASSERT_TRUE(library.Compile());
+    ASSERT_COMPILED_AND_CONVERT(library);
     auto decl_order = library.declaration_order();
     ASSERT_EQ(4, decl_order.size());
     ASSERT_DECL_NAME(decl_order[0], namer.of("Element"));
@@ -127,7 +127,7 @@
 
 )FIDL");
     TestLibrary library(source);
-    ASSERT_TRUE(library.Compile());
+    ASSERT_COMPILED(library);
     auto decl_order = library.declaration_order();
     ASSERT_EQ(4, decl_order.size());
 
@@ -170,7 +170,7 @@
 
 )FIDL");
     TestLibrary library(source);
-    ASSERT_TRUE(library.Compile());
+    ASSERT_COMPILED(library);
     auto decl_order = library.declaration_order();
     ASSERT_EQ(3, decl_order.size());
     ASSERT_DECL_NAME(decl_order[0], namer.of("Request"));
@@ -200,7 +200,7 @@
 
 )FIDL");
     TestLibrary library(source);
-    ASSERT_TRUE(library.Compile());
+    ASSERT_COMPILED(library);
     auto decl_order = library.declaration_order();
     ASSERT_EQ(4, decl_order.size());
     ASSERT_DECL_NAME(decl_order[0], namer.of("Payload"));
@@ -231,7 +231,7 @@
 
 )FIDL");
     TestLibrary library(source);
-    ASSERT_TRUE(library.Compile());
+    ASSERT_COMPILED(library);
     auto decl_order = library.declaration_order();
     ASSERT_EQ(4, decl_order.size());
 
@@ -281,7 +281,7 @@
 
 )FIDL");
     TestLibrary library(source);
-    ASSERT_TRUE(library.Compile());
+    ASSERT_COMPILED_AND_CONVERT(library);
     auto decl_order = library.declaration_order();
     ASSERT_EQ(5, decl_order.size());
     ASSERT_DECL_NAME(decl_order[0], namer.of("Payload"));
@@ -316,7 +316,7 @@
 
 )FIDL");
     TestLibrary library(source);
-    ASSERT_TRUE(library.Compile());
+    ASSERT_COMPILED_AND_CONVERT(library);
     auto decl_order = library.declaration_order();
     ASSERT_EQ(5, decl_order.size());
 
@@ -371,7 +371,7 @@
 )FIDL",
                         &shared);
     ASSERT_TRUE(library.AddDependentLibrary(std::move(dependency)));
-    ASSERT_TRUE(library.Compile());
+    ASSERT_COMPILED(library);
 
     auto decl_order = library.declaration_order();
     ASSERT_EQ(5, decl_order.size());
@@ -395,7 +395,7 @@
 
 )FIDL");
     TestLibrary library(source);
-    ASSERT_TRUE(library.Compile());
+    ASSERT_COMPILED_AND_CONVERT(library);
     auto decl_order = library.declaration_order();
     ASSERT_EQ(2, decl_order.size());
     ASSERT_DECL_NAME(decl_order[0], namer.of("Alias"));
@@ -415,7 +415,7 @@
 
 )FIDL");
     TestLibrary library(source);
-    ASSERT_TRUE(library.Compile());
+    ASSERT_COMPILED_AND_CONVERT(library);
     auto decl_order = library.declaration_order();
     ASSERT_EQ(2, decl_order.size());
     ASSERT_DECL_NAME(decl_order[0], namer.of("Alias"));
@@ -435,7 +435,7 @@
 
 )FIDL");
     TestLibrary library(source);
-    ASSERT_TRUE(library.Compile());
+    ASSERT_COMPILED_AND_CONVERT(library);
     auto decl_order = library.declaration_order();
     ASSERT_EQ(2, decl_order.size());
     ASSERT_DECL_NAME(decl_order[0], namer.of("Alias"));
diff --git a/zircon/system/utest/fidl-compiler/enums_tests.cc b/zircon/system/utest/fidl-compiler/enums_tests.cc
index 3bd9d2e..2ae9300 100644
--- a/zircon/system/utest/fidl-compiler/enums_tests.cc
+++ b/zircon/system/utest/fidl-compiler/enums_tests.cc
@@ -5,6 +5,7 @@
 #include <zxtest/zxtest.h>
 
 #include "error_test.h"
+#include "fidl/diagnostics.h"
 #include "test_library.h"
 
 namespace {
@@ -288,8 +289,7 @@
 };
 )FIDL",
                       experimental_flags);
-  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrCannotBeNullable);
-  ASSERT_SUBSTR(library.errors()[0]->msg.c_str(), "NotNullable");
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrCannotBeNullable)
 }
 
 TEST(EnumsTests, BadEnumShantBeNullableOld) {
@@ -308,4 +308,22 @@
   ASSERT_SUBSTR(library.errors()[0]->msg.c_str(), "NotNullable");
 }
 
+TEST(EnumsTests, BadEnumMultipleConstraints) {
+  fidl::ExperimentalFlags experimental_flags;
+  experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
+  TestLibrary library(R"FIDL(
+library example;
+
+type NotNullable = enum {
+    MEMBER = 1;
+};
+
+type Struct = struct {
+    not_nullable NotNullable:<optional, foo, bar>;
+};
+)FIDL",
+                      experimental_flags);
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrTooManyConstraints)
+}
+
 }  // namespace
diff --git a/zircon/system/utest/fidl-compiler/errors_tests.cc b/zircon/system/utest/fidl-compiler/errors_tests.cc
index 3b4b33c..96dff7c 100644
--- a/zircon/system/utest/fidl-compiler/errors_tests.cc
+++ b/zircon/system/utest/fidl-compiler/errors_tests.cc
@@ -28,9 +28,10 @@
   ASSERT_NOT_NULL(response);
   ASSERT_EQ(response->members.size(), 1);
   auto response_member = &response->members.at(0);
-  ASSERT_EQ(response_member->type_ctor->type->kind, fidl::flat::Type::Kind::kIdentifier);
-  auto result_identifier =
-      static_cast<const fidl::flat::IdentifierType*>(response_member->type_ctor->type);
+  ASSERT_EQ(fidl::flat::GetType(response_member->type_ctor)->kind,
+            fidl::flat::Type::Kind::kIdentifier);
+  auto result_identifier = static_cast<const fidl::flat::IdentifierType*>(
+      fidl::flat::GetType(response_member->type_ctor));
   const fidl::flat::Union* result_union =
       library.LookupUnion(std::string(result_identifier->name.decl_name()));
   ASSERT_NOT_NULL(result_union);
@@ -46,10 +47,11 @@
   ASSERT_NOT_NULL(error.maybe_used);
   ASSERT_STR_EQ("err", std::string(error.maybe_used->name.data()).c_str());
 
-  ASSERT_NOT_NULL(error.maybe_used->type_ctor->type);
-  ASSERT_EQ(error.maybe_used->type_ctor->type->kind, fidl::flat::Type::Kind::kPrimitive);
-  auto primitive_type =
-      static_cast<const fidl::flat::PrimitiveType*>(error.maybe_used->type_ctor->type);
+  ASSERT_NOT_NULL(fidl::flat::GetType(error.maybe_used->type_ctor));
+  ASSERT_EQ(fidl::flat::GetType(error.maybe_used->type_ctor)->kind,
+            fidl::flat::Type::Kind::kPrimitive);
+  auto primitive_type = static_cast<const fidl::flat::PrimitiveType*>(
+      fidl::flat::GetType(error.maybe_used->type_ctor));
   ASSERT_EQ(primitive_type->subtype, fidl::types::PrimitiveSubtype::kInt32);
 }
 
diff --git a/zircon/system/utest/fidl-compiler/handle_tests.cc b/zircon/system/utest/fidl-compiler/handle_tests.cc
index 15c709c..17e132cf 100644
--- a/zircon/system/utest/fidl-compiler/handle_tests.cc
+++ b/zircon/system/utest/fidl-compiler/handle_tests.cc
@@ -13,10 +13,14 @@
 #include <zxtest/zxtest.h>
 
 #include "error_test.h"
+#include "fidl/diagnostics.h"
+#include "fidl/experimental_flags.h"
 #include "test_library.h"
 
 namespace {
 
+using fidl::flat::GetType;
+
 TEST(HandleTests, GoodHandleRightsTest) {
   fidl::ExperimentalFlags experimental_flags;
   experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kEnableHandleRights);
@@ -32,14 +36,22 @@
                                std::move(experimental_flags));
   ASSERT_COMPILED_AND_CONVERT(library);
 
-  auto h_type_ctor = library.LookupStruct("MyStruct")->members[0].type_ctor.get();
+  const auto& h_type_ctor = library.LookupStruct("MyStruct")->members[0].type_ctor;
 
-  EXPECT_TRUE(h_type_ctor->handle_subtype_identifier.has_value());
-  EXPECT_EQ("THREAD", h_type_ctor->handle_subtype_identifier.value().span()->data());
+  std::visit(fidl::utils::matchers{[&](const std::unique_ptr<fidl::flat::TypeConstructorOld>& t) {
+                                     EXPECT_TRUE(t->handle_subtype_identifier.has_value());
+                                     EXPECT_EQ("THREAD",
+                                               t->handle_subtype_identifier.value().span()->data());
+                                   },
+                                   [](const std::unique_ptr<fidl::flat::TypeConstructorNew>& t) {
+                                     assert(false && "uncoverted copy should be used");
+                                   }},
+             h_type_ctor);
 
-  ASSERT_NOT_NULL(h_type_ctor->type);
-  ASSERT_EQ(h_type_ctor->type->kind, fidl::flat::Type::Kind::kHandle);
-  auto handle_type = static_cast<const fidl::flat::HandleType*>(h_type_ctor->type);
+  auto h_type = GetType(h_type_ctor);
+  ASSERT_NOT_NULL(h_type);
+  ASSERT_EQ(h_type->kind, fidl::flat::Type::Kind::kHandle);
+  auto handle_type = static_cast<const fidl::flat::HandleType*>(h_type);
 
   EXPECT_EQ(2, handle_type->obj_type);
   EXPECT_EQ(
@@ -64,13 +76,21 @@
 
   ASSERT_COMPILED_AND_CONVERT(library);
 
-  auto h_type_ctor = library.LookupStruct("MyStruct")->members[0].type_ctor.get();
-  ASSERT_NOT_NULL(h_type_ctor->type);
-  ASSERT_EQ(h_type_ctor->type->kind, fidl::flat::Type::Kind::kHandle);
-  auto handle_type = static_cast<const fidl::flat::HandleType*>(h_type_ctor->type);
+  const auto& h_type_ctor = library.LookupStruct("MyStruct")->members[0].type_ctor;
+  auto h_type = GetType(h_type_ctor);
+  ASSERT_NOT_NULL(h_type);
+  ASSERT_EQ(h_type->kind, fidl::flat::Type::Kind::kHandle);
+  auto handle_type = static_cast<const fidl::flat::HandleType*>(h_type);
 
-  EXPECT_TRUE(h_type_ctor->handle_subtype_identifier.has_value());
-  ASSERT_TRUE(h_type_ctor->handle_subtype_identifier.value().span()->data() == "VMO");
+  std::visit(fidl::utils::matchers{
+                 [&](const std::unique_ptr<fidl::flat::TypeConstructorOld>& t) {
+                   EXPECT_TRUE(t->handle_subtype_identifier.has_value());
+                   ASSERT_TRUE(t->handle_subtype_identifier.value().span()->data() == "VMO");
+                 },
+                 [](const std::unique_ptr<fidl::flat::TypeConstructorNew>& t) {
+                   assert(false && "uncoverted copy should be used");
+                 }},
+             h_type_ctor);
   EXPECT_EQ(3, handle_type->obj_type);
   EXPECT_EQ(
       static_cast<const fidl::flat::NumericConstantValue<uint32_t>*>(handle_type->rights)->value,
@@ -94,8 +114,10 @@
 )FIDL",
                                std::move(experimental_flags));
 
+  // NOTE(fxbug.dev/72924): we provide a more general error because there are multiple
+  // possible interpretations.
   ASSERT_ERRORED_TWICE_DURING_COMPILE(library, fidl::ErrConstantCannotBeInterpretedAsType,
-                                      fidl::ErrCouldNotResolveHandleRights);
+                                      fidl::ErrUnexpectedConstraint);
 }
 
 TEST(HandleTests, BadInvalidHandleRightsTestOld) {
@@ -133,11 +155,12 @@
                                std::move(experimental_flags));
   ASSERT_COMPILED_AND_CONVERT(library);
 
-  auto h_type_ctor = library.LookupStruct("MyStruct")->members[0].type_ctor.get();
+  const auto& h_type_ctor = library.LookupStruct("MyStruct")->members[0].type_ctor;
 
-  ASSERT_NOT_NULL(h_type_ctor->type);
-  ASSERT_EQ(h_type_ctor->type->kind, fidl::flat::Type::Kind::kHandle);
-  auto handle_type = static_cast<const fidl::flat::HandleType*>(h_type_ctor->type);
+  auto h_type = GetType(h_type_ctor);
+  ASSERT_NOT_NULL(h_type);
+  ASSERT_EQ(h_type->kind, fidl::flat::Type::Kind::kHandle);
+  auto handle_type = static_cast<const fidl::flat::HandleType*>(h_type);
 
   EXPECT_EQ(0, handle_type->obj_type);
   EXPECT_EQ(
@@ -163,39 +186,32 @@
                                std::move(experimental_flags));
 
   ASSERT_COMPILED_AND_CONVERT(library);
-  auto a = library.LookupStruct("MyStruct")->members[0].type_ctor.get();
-  EXPECT_TRUE(a->handle_subtype_identifier.has_value());
-  ASSERT_TRUE(a->handle_subtype_identifier.value().span()->data() == "THREAD");
-  ASSERT_NOT_NULL(a->type);
-  ASSERT_EQ(a->type->kind, fidl::flat::Type::Kind::kHandle);
-  auto a_handle_type = static_cast<const fidl::flat::HandleType*>(a->type);
+  const auto& a = library.LookupStruct("MyStruct")->members[0].type_ctor;
+  auto a_type = GetType(a);
+  ASSERT_NOT_NULL(a_type);
+  ASSERT_EQ(a_type->kind, fidl::flat::Type::Kind::kHandle);
+  auto a_handle_type = static_cast<const fidl::flat::HandleType*>(a_type);
   EXPECT_EQ(2, a_handle_type->obj_type);
-  EXPECT_EQ(
-      static_cast<const fidl::flat::NumericConstantValue<uint32_t>*>(a_handle_type->rights)->value,
-      fidl::flat::kHandleSameRights);
+  EXPECT_EQ(static_cast<const fidl::flat::HandleRights*>(a_handle_type->rights)->value,
+            fidl::flat::kHandleSameRights);
 
-  auto b = library.LookupStruct("MyStruct")->members[1].type_ctor.get();
-  EXPECT_TRUE(b->handle_subtype_identifier.has_value());
-  ASSERT_TRUE(b->handle_subtype_identifier.value().span()->data() == "PROCESS");
-  ASSERT_NOT_NULL(b->type);
-  ASSERT_EQ(b->type->kind, fidl::flat::Type::Kind::kHandle);
-  auto b_handle_type = static_cast<const fidl::flat::HandleType*>(b->type);
+  const auto& b = library.LookupStruct("MyStruct")->members[1].type_ctor;
+  auto b_type = GetType(b);
+  ASSERT_NOT_NULL(b_type);
+  ASSERT_EQ(b_type->kind, fidl::flat::Type::Kind::kHandle);
+  auto b_handle_type = static_cast<const fidl::flat::HandleType*>(b_type);
   EXPECT_EQ(1, b_handle_type->obj_type);
-  EXPECT_EQ(
-      static_cast<const fidl::flat::NumericConstantValue<uint32_t>*>(b_handle_type->rights)->value,
-      fidl::flat::kHandleSameRights);
+  EXPECT_EQ(static_cast<const fidl::flat::HandleRights*>(b_handle_type->rights)->value,
+            fidl::flat::kHandleSameRights);
 
-  auto c = library.LookupStruct("MyStruct")->members[2].type_ctor.get();
-  EXPECT_TRUE(c->handle_subtype_identifier.has_value());
-  ASSERT_TRUE(c->handle_subtype_identifier.value().span()->data() == "VMO");
-  ASSERT_NOT_NULL(c->type);
-  ASSERT_EQ(c->type->kind, fidl::flat::Type::Kind::kHandle);
-  auto c_handle_type = static_cast<const fidl::flat::HandleType*>(c->type);
+  const auto& c = library.LookupStruct("MyStruct")->members[2].type_ctor;
+  auto c_type = GetType(c);
+  ASSERT_NOT_NULL(c_type);
+  ASSERT_EQ(c_type->kind, fidl::flat::Type::Kind::kHandle);
+  auto c_handle_type = static_cast<const fidl::flat::HandleType*>(c_type);
   EXPECT_EQ(3, c_handle_type->obj_type);
   ASSERT_NOT_NULL(c_handle_type->rights);
-  EXPECT_EQ(
-      static_cast<const fidl::flat::NumericConstantValue<uint32_t>*>(c_handle_type->rights)->value,
-      2);
+  EXPECT_EQ(static_cast<const fidl::flat::HandleRights*>(c_handle_type->rights)->value, 2);
 }
 
 TEST(HandleTests, BadInvalidFidlDefinedHandleSubtype) {
@@ -214,8 +230,9 @@
 )FIDL",
                                std::move(experimental_flags));
 
-  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrCouldNotResolveHandleSubtype);
-  EXPECT_TRUE(library.errors()[0]->msg.find("ZIPPY") != std::string::npos);
+  // NOTE(fxbug.dev/72924): we provide a more general error because there are multiple
+  // possible interpretations.
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrUnexpectedConstraint);
 }
 
 TEST(HandleTests, BadInvalidFidlDefinedHandleSubtypeOld) {
@@ -281,13 +298,21 @@
 
   ASSERT_COMPILED_AND_CONVERT(library);
 
-  auto h_type_ctor = library.LookupStruct("MyStruct")->members[0].type_ctor.get();
-  ASSERT_NOT_NULL(h_type_ctor->type);
-  ASSERT_EQ(h_type_ctor->type->kind, fidl::flat::Type::Kind::kHandle);
-  auto handle_type = static_cast<const fidl::flat::HandleType*>(h_type_ctor->type);
+  const auto& h_type_ctor = library.LookupStruct("MyStruct")->members[0].type_ctor;
+  auto h_type = GetType(h_type_ctor);
+  ASSERT_NOT_NULL(h_type);
+  ASSERT_EQ(h_type->kind, fidl::flat::Type::Kind::kHandle);
+  auto handle_type = static_cast<const fidl::flat::HandleType*>(h_type);
 
-  EXPECT_TRUE(h_type_ctor->handle_subtype_identifier.has_value());
-  ASSERT_TRUE(h_type_ctor->handle_subtype_identifier.value().span()->data() == "VMO");
+  std::visit(fidl::utils::matchers{
+                 [&](const std::unique_ptr<fidl::flat::TypeConstructorOld>& t) {
+                   EXPECT_TRUE(t->handle_subtype_identifier.has_value());
+                   ASSERT_TRUE(t->handle_subtype_identifier.value().span()->data() == "VMO");
+                 },
+                 [](const std::unique_ptr<fidl::flat::TypeConstructorNew>& t) {
+                   assert(false && "uncoverted copy should be used");
+                 }},
+             h_type_ctor);
   EXPECT_EQ(3, handle_type->obj_type);
   EXPECT_EQ(
       static_cast<const fidl::flat::NumericConstantValue<uint32_t>*>(handle_type->rights)->value,
@@ -319,8 +344,10 @@
 )FIDL",
                       std::move(experimental_flags));
 
+  // NOTE(fxbug.dev/72924): we provide a more general error because there are multiple
+  // possible interpretations.
   ASSERT_ERRORED_TWICE_DURING_COMPILE(library, fidl::ErrResourceMissingRightsProperty,
-                                      fidl::ErrCouldNotResolveHandleRights);
+                                      fidl::ErrUnexpectedConstraint);
 }
 
 TEST(HandleTests, BadResourceDefinitionMissingRightsPropertyTestOld) {
@@ -372,8 +399,10 @@
 )FIDL",
                       std::move(experimental_flags));
 
+  // NOTE(fxbug.dev/72924): we provide a more general error because there are multiple
+  // possible interpretations.
   ASSERT_ERRORED_TWICE_DURING_COMPILE(library, fidl::ErrResourceMissingSubtypeProperty,
-                                      fidl::ErrCouldNotResolveHandleSubtype);
+                                      fidl::ErrUnexpectedConstraint);
 }
 
 TEST(HandleTests, BadResourceDefinitionMissingSubtypePropertyTestOld) {
@@ -399,4 +428,72 @@
                                       fidl::ErrCouldNotResolveHandleSubtype);
 }
 
+// TODO(fxbug.dev/74909): turn this into a Bad test
+TEST(HandleTests, GoodBareHandleNoConstraints) {
+  TestLibrary library(R"FIDL(
+library example;
+
+resource struct MyStruct {
+    handle h;
+};
+)FIDL");
+  ASSERT_COMPILED_AND_CONVERT(library);
+}
+
+TEST(HandleTests, BadBareHandleWithConstraintsOld) {
+  TestLibrary library(R"FIDL(
+library example;
+
+resource struct MyStruct {
+    handle:VMO h;
+};
+)FIDL");
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrHandleSubtypeNotResource);
+}
+
+TEST(HandleTests, BadBareHandleWithConstraints) {
+  fidl::ExperimentalFlags experimental_flags;
+  experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
+  TestLibrary library(R"FIDL(
+library example;
+
+type MyStruct = resource struct {
+    h handle:VMO;
+};
+)FIDL",
+                      experimental_flags);
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrHandleSubtypeNotResource);
+}
+
+TEST(HandleTests, BadBareHandleWithConstraintsThroughAliasOld) {
+  TestLibrary library(R"FIDL(
+library example;
+
+alias my_handle = handle;
+
+resource struct MyStruct {
+    my_handle:VMO h;
+};
+)FIDL");
+  // NOTE(fxbug.dev/72924): The old syntax fails in a different way because of the way it parses
+  // handles, assuming that it's a size bound since it doesn't match "handle" exactly.
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrCouldNotParseSizeBound);
+}
+
+TEST(HandleTests, BadBareHandleWithConstraintsThroughAlias) {
+  fidl::ExperimentalFlags experimental_flags;
+  experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
+  TestLibrary library(R"FIDL(
+library example;
+
+alias my_handle = handle;
+
+type MyStruct = resource struct {
+    h my_handle:VMO;
+};
+)FIDL",
+                      experimental_flags);
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrHandleSubtypeNotResource);
+}
+
 }  // namespace
diff --git a/zircon/system/utest/fidl-compiler/new_syntax_tests.cc b/zircon/system/utest/fidl-compiler/new_syntax_tests.cc
index 00cbecc..e1b637f 100644
--- a/zircon/system/utest/fidl-compiler/new_syntax_tests.cc
+++ b/zircon/system/utest/fidl-compiler/new_syntax_tests.cc
@@ -14,10 +14,15 @@
 
 #include "error_test.h"
 #include "fidl/diagnostics.h"
+#include "fidl/flat_ast.h"
 #include "test_library.h"
 
 namespace {
 
+using fidl::flat::GetLayoutInvocation;
+using fidl::flat::GetName;
+using fidl::flat::GetType;
+
 TEST(NewSyntaxTests, GoodSyntaxVersionOmitted) {
   fidl::ExperimentalFlags experimental_flags;
   experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
@@ -161,7 +166,7 @@
   auto type_decl = library.LookupBits("TypeDecl");
   ASSERT_NOT_NULL(type_decl);
   EXPECT_EQ(type_decl->members.size(), 2);
-  EXPECT_EQ(type_decl->subtype_ctor->name.decl_name(), "uint64");
+  EXPECT_EQ(GetName(type_decl->subtype_ctor).decl_name(), "uint64");
 }
 
 TEST(NewSyntaxTests, GoodTypeDeclOfBitsLayoutWithStrictnesss) {
@@ -229,7 +234,7 @@
   auto type_decl = library.LookupEnum("TypeDecl");
   ASSERT_NOT_NULL(type_decl);
   EXPECT_EQ(type_decl->members.size(), 2);
-  EXPECT_EQ(type_decl->subtype_ctor->name.decl_name(), "int32");
+  EXPECT_EQ(GetName(type_decl->subtype_ctor).decl_name(), "int32");
 }
 
 TEST(NewSyntaxTests, BadTypeDeclOfEnumLayoutWithInvalidSubtype) {
@@ -631,37 +636,34 @@
   // TODO(fxbug.dev/65978): a number of fields in this struct declaration have
   //  been commented out until their respective features (client/server_end)
   //  have been added to the compiler.
-  auto library = WithLibraryZx(R"FIDL(
+  TestLibrary library(R"FIDL(
 library example;
-using zx;
+
+alias TypeAlias = vector<uint8>;
 type t1 = resource struct {
-  h0 zx.handle;
-  h1 zx.handle:optional;
-  h2 zx.handle:VMO;
-  h3 zx.handle:<VMO,optional>;
-  h4 zx.handle:<VMO,zx.rights.DUPLICATE>;
-  h5 zx.handle:<VMO,zx.rights.DUPLICATE,optional>;
   u7 union { 1: b bool; };
-  u8 union { 1: b bool; }:optional;
-  v9 vector<bool>;
-  v10 vector<bool>:optional;
-  v11 vector<bool>:16;
-  v12 vector<bool>:<16,optional>;
-  //p13 client_end:MyProtocol;
-  //p14 client_end:<MyProtocol,optional>;
-  //r15 server_end:P;
-  //r16 server_end:<MyProtocol,optional>;
+  // TODO(fxbug.dev/74683):
+  // u8 union { 1: b bool; }:optional;
+  // p13 client_end:MyProtocol;
+  // p14 client_end:<MyProtocol,optional>;
+  // r15 server_end:P;
+  // r16 server_end:<MyProtocol,optional>;
 };
 )FIDL",
-                               std::move(experimental_flags));
+                      experimental_flags);
   ASSERT_COMPILED(library);
 
   auto type_decl = library.LookupStruct("t1");
   ASSERT_NOT_NULL(type_decl);
-  EXPECT_EQ(type_decl->members.size(), 12);
-  // TODO(fxbug.dev/65978): check that the flat AST has proper representation of
-  //  each member's constraints. This is blocked on implementing compilation of
-  //  the new constraints in the flat AST.
+  EXPECT_EQ(type_decl->members.size(), 1);
+
+  size_t i = 0;
+
+  auto u7_type_base = GetType(type_decl->members[i++].type_ctor);
+  ASSERT_EQ(u7_type_base->kind, fidl::flat::Type::Kind::kIdentifier);
+  auto u7_type = static_cast<const fidl::flat::IdentifierType*>(u7_type_base);
+  EXPECT_EQ(u7_type->nullability, fidl::types::Nullability::kNonnullable);
+  EXPECT_EQ(u7_type->type_decl->kind, fidl::flat::Decl::Kind::kUnion);
 }
 
 // This test ensures that recoverable parsing works as intended for constraints,
@@ -792,69 +794,126 @@
   ASSERT_NOT_NULL(type_decl);
   ASSERT_EQ(type_decl->members.size(), 16);
 
-  auto& v0 = type_decl->members[0];
-  ASSERT_NULL(v0.type_ctor->maybe_size);
-  EXPECT_EQ(v0.type_ctor->nullability, fidl::types::Nullability::kNonnullable);
+  size_t i = 0;
 
-  auto& v1 = type_decl->members[1];
-  ASSERT_NOT_NULL(v1.type_ctor->maybe_size);
-  EXPECT_EQ(v1.type_ctor->nullability, fidl::types::Nullability::kNonnullable);
+  auto v0_type_base = GetType(type_decl->members[i++].type_ctor);
+  ASSERT_EQ(v0_type_base->kind, fidl::flat::Type::Kind::kVector);
+  auto v0_type = static_cast<const fidl::flat::VectorType*>(v0_type_base);
+  EXPECT_EQ(v0_type->nullability, fidl::types::Nullability::kNonnullable);
+  EXPECT_EQ(v0_type->element_type->kind, fidl::flat::Type::Kind::kPrimitive);
+  EXPECT_EQ(v0_type->element_count, &fidl::flat::VectorType::kMaxSize);
 
-  auto& v2 = type_decl->members[2];
-  ASSERT_NULL(v2.type_ctor->maybe_size);
-  EXPECT_EQ(v2.type_ctor->nullability, fidl::types::Nullability::kNullable);
+  auto v1_type_base = GetType(type_decl->members[i++].type_ctor);
+  ASSERT_EQ(v1_type_base->kind, fidl::flat::Type::Kind::kVector);
+  auto v1_type = static_cast<const fidl::flat::VectorType*>(v1_type_base);
+  EXPECT_EQ(v1_type->nullability, fidl::types::Nullability::kNonnullable);
+  EXPECT_EQ(v1_type->element_type->kind, fidl::flat::Type::Kind::kPrimitive);
+  EXPECT_EQ(v1_type->element_count->value, 16u);
 
-  auto& v3 = type_decl->members[3];
-  ASSERT_NOT_NULL(v3.type_ctor->maybe_size);
-  EXPECT_EQ(v3.type_ctor->nullability, fidl::types::Nullability::kNullable);
+  auto v2_type_base = GetType(type_decl->members[i++].type_ctor);
+  ASSERT_EQ(v2_type_base->kind, fidl::flat::Type::Kind::kVector);
+  auto v2_type = static_cast<const fidl::flat::VectorType*>(v2_type_base);
+  EXPECT_EQ(v2_type->nullability, fidl::types::Nullability::kNullable);
+  EXPECT_EQ(v2_type->element_type->kind, fidl::flat::Type::Kind::kPrimitive);
+  EXPECT_EQ(v2_type->element_count, &fidl::flat::VectorType::kMaxSize);
 
-  auto& b4 = type_decl->members[4];
-  ASSERT_NULL(b4.type_ctor->maybe_size);
-  EXPECT_EQ(b4.type_ctor->nullability, fidl::types::Nullability::kNonnullable);
+  auto v3_type_base = GetType(type_decl->members[i++].type_ctor);
+  ASSERT_EQ(v3_type_base->kind, fidl::flat::Type::Kind::kVector);
+  auto v3_type = static_cast<const fidl::flat::VectorType*>(v3_type_base);
+  EXPECT_EQ(v3_type->nullability, fidl::types::Nullability::kNullable);
+  EXPECT_EQ(v3_type->element_count->value, 16u);
 
-  auto& b5 = type_decl->members[5];
-  ASSERT_NOT_NULL(b5.type_ctor->maybe_size);
-  EXPECT_EQ(b5.type_ctor->nullability, fidl::types::Nullability::kNonnullable);
+  auto b4_type_base = GetType(type_decl->members[i++].type_ctor);
+  ASSERT_EQ(b4_type_base->kind, fidl::flat::Type::Kind::kVector);
+  auto b4_type = static_cast<const fidl::flat::VectorType*>(b4_type_base);
+  EXPECT_EQ(b4_type->nullability, fidl::types::Nullability::kNonnullable);
+  EXPECT_EQ(b4_type->element_count, &fidl::flat::VectorType::kMaxSize);
 
-  auto& b6 = type_decl->members[6];
-  ASSERT_NULL(b6.type_ctor->maybe_size);
-  EXPECT_EQ(b6.type_ctor->nullability, fidl::types::Nullability::kNullable);
+  auto b5_type_base = GetType(type_decl->members[i++].type_ctor);
+  ASSERT_EQ(b5_type_base->kind, fidl::flat::Type::Kind::kVector);
+  auto b5_type = static_cast<const fidl::flat::VectorType*>(b5_type_base);
+  EXPECT_EQ(b5_type->nullability, fidl::types::Nullability::kNonnullable);
+  EXPECT_EQ(b5_type->element_count->value, 16u);
 
-  auto& b7 = type_decl->members[7];
-  ASSERT_NOT_NULL(b7.type_ctor->maybe_size);
-  EXPECT_EQ(b7.type_ctor->nullability, fidl::types::Nullability::kNullable);
+  auto b6_type_base = GetType(type_decl->members[i++].type_ctor);
+  ASSERT_EQ(b6_type_base->kind, fidl::flat::Type::Kind::kVector);
+  auto b6_type = static_cast<const fidl::flat::VectorType*>(b6_type_base);
+  EXPECT_EQ(b6_type->nullability, fidl::types::Nullability::kNullable);
+  EXPECT_EQ(b6_type->element_count, &fidl::flat::VectorType::kMaxSize);
 
-  auto& s8 = type_decl->members[8];
-  ASSERT_NULL(s8.type_ctor->maybe_size);
-  EXPECT_EQ(s8.type_ctor->nullability, fidl::types::Nullability::kNonnullable);
+  auto b7_type_base = GetType(type_decl->members[i++].type_ctor);
+  ASSERT_EQ(b7_type_base->kind, fidl::flat::Type::Kind::kVector);
+  auto b7_type = static_cast<const fidl::flat::VectorType*>(b7_type_base);
+  EXPECT_EQ(b7_type->nullability, fidl::types::Nullability::kNullable);
+  EXPECT_EQ(b7_type->element_count->value, 16u);
 
-  auto& s9 = type_decl->members[9];
-  ASSERT_NOT_NULL(s9.type_ctor->maybe_size);
-  EXPECT_EQ(s9.type_ctor->nullability, fidl::types::Nullability::kNonnullable);
+  auto s8_type_base = GetType(type_decl->members[i++].type_ctor);
+  ASSERT_EQ(s8_type_base->kind, fidl::flat::Type::Kind::kString);
+  auto s8_type = static_cast<const fidl::flat::StringType*>(s8_type_base);
+  EXPECT_EQ(s8_type->nullability, fidl::types::Nullability::kNonnullable);
+  EXPECT_EQ(s8_type->max_size, &fidl::flat::StringType::kMaxSize);
 
-  auto& s10 = type_decl->members[10];
-  ASSERT_NULL(s10.type_ctor->maybe_size);
-  EXPECT_EQ(s10.type_ctor->nullability, fidl::types::Nullability::kNullable);
+  auto s9_type_base = GetType(type_decl->members[i++].type_ctor);
+  ASSERT_EQ(s9_type_base->kind, fidl::flat::Type::Kind::kString);
+  auto s9_type = static_cast<const fidl::flat::StringType*>(s9_type_base);
+  EXPECT_EQ(s9_type->nullability, fidl::types::Nullability::kNonnullable);
+  EXPECT_EQ(s9_type->max_size->value, 16u);
 
-  auto& s11 = type_decl->members[11];
-  ASSERT_NOT_NULL(s11.type_ctor->maybe_size);
-  EXPECT_EQ(s11.type_ctor->nullability, fidl::types::Nullability::kNullable);
+  auto s10_type_base = GetType(type_decl->members[i++].type_ctor);
+  ASSERT_EQ(s10_type_base->kind, fidl::flat::Type::Kind::kString);
+  auto s10_type = static_cast<const fidl::flat::StringType*>(s10_type_base);
+  EXPECT_EQ(s10_type->nullability, fidl::types::Nullability::kNullable);
+  EXPECT_EQ(s10_type->max_size, &fidl::flat::StringType::kMaxSize);
 
-  auto& a12 = type_decl->members[12];
-  ASSERT_NULL(a12.type_ctor->maybe_size);
-  EXPECT_EQ(a12.type_ctor->nullability, fidl::types::Nullability::kNonnullable);
+  auto s11_type_base = GetType(type_decl->members[i++].type_ctor);
+  ASSERT_EQ(s11_type_base->kind, fidl::flat::Type::Kind::kString);
+  auto s11_type = static_cast<const fidl::flat::StringType*>(s11_type_base);
+  EXPECT_EQ(s11_type->nullability, fidl::types::Nullability::kNullable);
+  EXPECT_EQ(s11_type->max_size->value, 16u);
 
-  auto& a13 = type_decl->members[13];
-  ASSERT_NOT_NULL(a13.type_ctor->maybe_size);
-  EXPECT_EQ(a13.type_ctor->nullability, fidl::types::Nullability::kNonnullable);
+  auto a12_invocation = GetLayoutInvocation(type_decl->members[i].type_ctor);
+  EXPECT_NULL(a12_invocation.element_type_resolved);
+  EXPECT_EQ(a12_invocation.nullability, fidl::types::Nullability::kNonnullable);
+  auto a12_type_base = GetType(type_decl->members[i++].type_ctor);
+  ASSERT_EQ(a12_type_base->kind, fidl::flat::Type::Kind::kVector);
+  auto a12_type = static_cast<const fidl::flat::VectorType*>(a12_type_base);
+  EXPECT_EQ(a12_type->nullability, fidl::types::Nullability::kNonnullable);
+  EXPECT_EQ(a12_type->element_type->kind, fidl::flat::Type::Kind::kPrimitive);
+  EXPECT_EQ(a12_type->element_count, &fidl::flat::VectorType::kMaxSize);
+  EXPECT_NULL(a12_invocation.size_resolved);
 
-  auto& a14 = type_decl->members[14];
-  ASSERT_NULL(a14.type_ctor->maybe_size);
-  EXPECT_EQ(a14.type_ctor->nullability, fidl::types::Nullability::kNullable);
+  auto a13_invocation = GetLayoutInvocation(type_decl->members[i].type_ctor);
+  EXPECT_NULL(a13_invocation.element_type_resolved);
+  EXPECT_EQ(a13_invocation.nullability, fidl::types::Nullability::kNonnullable);
+  auto a13_type_base = GetType(type_decl->members[i++].type_ctor);
+  ASSERT_EQ(a13_type_base->kind, fidl::flat::Type::Kind::kVector);
+  auto a13_type = static_cast<const fidl::flat::VectorType*>(a13_type_base);
+  EXPECT_EQ(a13_type->nullability, fidl::types::Nullability::kNonnullable);
+  EXPECT_EQ(a13_type->element_type->kind, fidl::flat::Type::Kind::kPrimitive);
+  EXPECT_EQ(a13_type->element_count->value, 16u);
+  EXPECT_EQ(a13_type->element_count, a13_invocation.size_resolved);
 
-  auto& a11 = type_decl->members[11];
-  ASSERT_NOT_NULL(a11.type_ctor->maybe_size);
-  EXPECT_EQ(a11.type_ctor->nullability, fidl::types::Nullability::kNullable);
+  auto a14_invocation = GetLayoutInvocation(type_decl->members[i].type_ctor);
+  EXPECT_NULL(a14_invocation.element_type_resolved);
+  EXPECT_EQ(a14_invocation.nullability, fidl::types::Nullability::kNullable);
+  auto a14_type_base = GetType(type_decl->members[i++].type_ctor);
+  ASSERT_EQ(a14_type_base->kind, fidl::flat::Type::Kind::kVector);
+  auto a14_type = static_cast<const fidl::flat::VectorType*>(a14_type_base);
+  EXPECT_EQ(a14_type->nullability, fidl::types::Nullability::kNullable);
+  EXPECT_EQ(a14_type->element_type->kind, fidl::flat::Type::Kind::kPrimitive);
+  EXPECT_EQ(a14_type->element_count, &fidl::flat::VectorType::kMaxSize);
+  // EXPECT_EQ(a14_type->element_count, a14_invocation->maybe_size);
+  EXPECT_NULL(a14_invocation.size_resolved);
+
+  auto a15_invocation = GetLayoutInvocation(type_decl->members[i].type_ctor);
+  EXPECT_NULL(a15_invocation.element_type_resolved);
+  EXPECT_EQ(a15_invocation.nullability, fidl::types::Nullability::kNullable);
+  auto a15_type_base = GetType(type_decl->members[i++].type_ctor);
+  ASSERT_EQ(a15_type_base->kind, fidl::flat::Type::Kind::kVector);
+  auto a15_type = static_cast<const fidl::flat::VectorType*>(a15_type_base);
+  EXPECT_EQ(a15_type->nullability, fidl::types::Nullability::kNullable);
+  EXPECT_EQ(a15_type->element_count->value, 16u);
+  EXPECT_EQ(a15_type->element_count, a15_invocation.size_resolved);
 }
 
 TEST(NewSyntaxTests, GoodConstraintsOnUnions) {
@@ -868,7 +927,8 @@
 alias UnionAlias = UnionDecl;
 type TypeDecl= struct {
   u0 union{1: bar bool;};
-  u1 union{1: baz bool;}:optional;
+  // TODO(fxbug.dev/74683)
+  // u1 union{1: baz bool;}:optional;
   u2 UnionDecl;
   u3 UnionDecl:optional;
   u4 UnionAlias;
@@ -880,25 +940,28 @@
   ASSERT_COMPILED(library);
   auto type_decl = library.LookupStruct("TypeDecl");
   ASSERT_NOT_NULL(type_decl);
-  ASSERT_EQ(type_decl->members.size(), 6);
+  ASSERT_EQ(type_decl->members.size(), 5);
+  size_t i = 0;
 
-  auto& u0 = type_decl->members[0];
-  EXPECT_EQ(u0.type_ctor->nullability, fidl::types::Nullability::kNonnullable);
+  auto& u0 = type_decl->members[i++];
+  auto u0_type = static_cast<const fidl::flat::IdentifierType*>(GetType(u0.type_ctor));
+  EXPECT_EQ(u0_type->nullability, fidl::types::Nullability::kNonnullable);
 
-  auto& u1 = type_decl->members[1];
-  EXPECT_EQ(u1.type_ctor->nullability, fidl::types::Nullability::kNullable);
+  auto& u2 = type_decl->members[i++];
+  auto u2_type = static_cast<const fidl::flat::IdentifierType*>(GetType(u2.type_ctor));
+  EXPECT_EQ(u2_type->nullability, fidl::types::Nullability::kNonnullable);
 
-  auto& u2 = type_decl->members[2];
-  EXPECT_EQ(u2.type_ctor->nullability, fidl::types::Nullability::kNonnullable);
+  auto& u3 = type_decl->members[i++];
+  auto u3_type = static_cast<const fidl::flat::IdentifierType*>(GetType(u3.type_ctor));
+  EXPECT_EQ(u3_type->nullability, fidl::types::Nullability::kNullable);
 
-  auto& u3 = type_decl->members[3];
-  EXPECT_EQ(u3.type_ctor->nullability, fidl::types::Nullability::kNullable);
+  auto& u4 = type_decl->members[i++];
+  auto u4_type = static_cast<const fidl::flat::IdentifierType*>(GetType(u4.type_ctor));
+  EXPECT_EQ(u4_type->nullability, fidl::types::Nullability::kNonnullable);
 
-  auto& u4 = type_decl->members[4];
-  EXPECT_EQ(u4.type_ctor->nullability, fidl::types::Nullability::kNonnullable);
-
-  auto& u5 = type_decl->members[5];
-  EXPECT_EQ(u5.type_ctor->nullability, fidl::types::Nullability::kNullable);
+  auto& u5 = type_decl->members[i++];
+  auto u5_type = static_cast<const fidl::flat::IdentifierType*>(GetType(u5.type_ctor));
+  EXPECT_EQ(u5_type->nullability, fidl::types::Nullability::kNullable);
 }
 
 TEST(NewSyntaxTests, GoodConstraintsOnHandles) {
@@ -927,34 +990,40 @@
   ASSERT_EQ(type_decl->members.size(), 6);
 
   auto& h0 = type_decl->members[0];
-  ASSERT_EQ(h0.type_ctor->handle_subtype_identifier, std::nullopt);
-  ASSERT_NULL(h0.type_ctor->handle_rights);
-  EXPECT_EQ(h0.type_ctor->nullability, fidl::types::Nullability::kNonnullable);
+  auto h0_type = static_cast<const fidl::flat::HandleType*>(GetType(h0.type_ctor));
+  EXPECT_EQ(h0_type->obj_type, 0u);
+  EXPECT_EQ(h0_type->rights, &fidl::flat::HandleType::kSameRights);
+  EXPECT_EQ(h0_type->nullability, fidl::types::Nullability::kNonnullable);
 
   auto& h1 = type_decl->members[1];
-  ASSERT_NE(h1.type_ctor->handle_subtype_identifier, std::nullopt);
-  ASSERT_NULL(h1.type_ctor->handle_rights);
-  EXPECT_EQ(h1.type_ctor->nullability, fidl::types::Nullability::kNonnullable);
+  auto h1_type = static_cast<const fidl::flat::HandleType*>(GetType(h1.type_ctor));
+  EXPECT_NE(h1_type->obj_type, 0u);
+  EXPECT_EQ(h1_type->rights, &fidl::flat::HandleType::kSameRights);
+  EXPECT_EQ(h1_type->nullability, fidl::types::Nullability::kNonnullable);
 
   auto& h2 = type_decl->members[2];
-  ASSERT_EQ(h2.type_ctor->handle_subtype_identifier, std::nullopt);
-  ASSERT_NULL(h2.type_ctor->handle_rights);
-  EXPECT_EQ(h2.type_ctor->nullability, fidl::types::Nullability::kNullable);
+  auto h2_type = static_cast<const fidl::flat::HandleType*>(GetType(h2.type_ctor));
+  EXPECT_EQ(h2_type->obj_type, 0u);
+  EXPECT_EQ(h2_type->rights, &fidl::flat::HandleType::kSameRights);
+  EXPECT_EQ(h2_type->nullability, fidl::types::Nullability::kNullable);
 
   auto& h3 = type_decl->members[3];
-  ASSERT_NE(h3.type_ctor->handle_subtype_identifier, std::nullopt);
-  ASSERT_NULL(h3.type_ctor->handle_rights);
-  EXPECT_EQ(h3.type_ctor->nullability, fidl::types::Nullability::kNullable);
+  auto h3_type = static_cast<const fidl::flat::HandleType*>(GetType(h3.type_ctor));
+  EXPECT_EQ(h3_type->obj_type, 3u);  // VMO
+  EXPECT_EQ(h3_type->rights, &fidl::flat::HandleType::kSameRights);
+  EXPECT_EQ(h3_type->nullability, fidl::types::Nullability::kNullable);
 
   auto& h4 = type_decl->members[4];
-  ASSERT_NE(h4.type_ctor->handle_subtype_identifier, std::nullopt);
-  ASSERT_NOT_NULL(h4.type_ctor->handle_rights);
-  EXPECT_EQ(h4.type_ctor->nullability, fidl::types::Nullability::kNonnullable);
+  auto h4_type = static_cast<const fidl::flat::HandleType*>(GetType(h4.type_ctor));
+  EXPECT_EQ(h4_type->obj_type, 3u);          // VMO
+  EXPECT_EQ(h4_type->rights->value, 0x02u);  // TRANSFER
+  EXPECT_EQ(h4_type->nullability, fidl::types::Nullability::kNonnullable);
 
   auto& h5 = type_decl->members[5];
-  ASSERT_NE(h5.type_ctor->handle_subtype_identifier, std::nullopt);
-  ASSERT_NOT_NULL(h5.type_ctor->handle_rights);
-  EXPECT_EQ(h5.type_ctor->nullability, fidl::types::Nullability::kNullable);
+  auto h5_type = static_cast<const fidl::flat::HandleType*>(GetType(h5.type_ctor));
+  EXPECT_EQ(h5_type->obj_type, 3u);          // VMO
+  EXPECT_EQ(h5_type->rights->value, 0x02u);  // TRANSFER
+  EXPECT_EQ(h5_type->nullability, fidl::types::Nullability::kNullable);
 }
 
 // TODO(fxbug.dev/71536): once the new flat AST is in, we should add a test for
@@ -995,24 +1064,25 @@
 // Ensure that we don't accidentally enable the old syntax when the new syntax
 // flag is enabled.
 TEST(NewSyntaxTests, GoodTypedChannelOldInNew) {
-  fidl::ExperimentalFlags experimental_flags;
-  experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
+  // TODO(fcz): enable in follow up
+  //   fidl::ExperimentalFlags experimental_flags;
+  //   experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
 
-  TestLibrary library(R"FIDL(
-library test;
+  //   TestLibrary library(R"FIDL(
+  // library test;
 
-protocol MyProtocol {};
+  // protocol MyProtocol {};
 
-type Foo = struct {
-  foo MyProtocol;
-};
+  // type Foo = struct {
+  //   foo MyProtocol;
+  // };
 
-)FIDL",
-                      std::move(experimental_flags));
-  EXPECT_FALSE(library.Compile());
-  const auto& errors = library.errors();
-  ASSERT_EQ(errors.size(), 1);
-  ASSERT_ERR(errors[0], fidl::ErrCannotUseProtocol);
+  // )FIDL",
+  //                       std::move(experimental_flags));
+  //   EXPECT_FALSE(library.Compile());
+  //   const auto& errors = library.errors();
+  //   ASSERT_EQ(errors.size(), 1);
+  //   ASSERT_ERR(errors[0], fidl::ErrCannotUseProtocol);
 }
 
 // The new syntax works when the new syntax flag is enabled.
@@ -1020,4 +1090,193 @@
   // TODO(fcz): make accompanying typespace change
 }
 
+TEST(NewSyntaxTests, BadTooManyLayoutParameters) {
+  fidl::ExperimentalFlags experimental_flags;
+  experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
+
+  TestLibrary library(R"FIDL(
+library example;
+
+type Foo = struct {
+  foo uint8<8>;
+};
+)FIDL",
+                      std::move(experimental_flags));
+
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrWrongNumberOfLayoutParameters);
+}
+
+TEST(NewSyntaxTests, BadNotEnoughParameters) {
+  fidl::ExperimentalFlags experimental_flags;
+  experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
+
+  TestLibrary library(R"FIDL(
+library example;
+
+type Foo = struct {
+  foo array<8>;
+};
+)FIDL",
+                      std::move(experimental_flags));
+
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrWrongNumberOfLayoutParameters);
+}
+
+TEST(NewSyntaxTests, BadTooManyConstraints) {
+  fidl::ExperimentalFlags experimental_flags;
+  experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
+
+  TestLibrary library(R"FIDL(
+library example;
+
+type Foo = struct {
+  foo uint8:<1, 2, 3>;
+};
+)FIDL",
+                      std::move(experimental_flags));
+
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrTooManyConstraints);
+}
+
+TEST(NewSyntaxTests, BadParameterizedAnonymousLayout) {
+  fidl::ExperimentalFlags experimental_flags;
+  experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
+
+  TestLibrary library(R"FIDL(
+library example;
+
+type Foo = struct {
+  foo struct {}<1>;
+};
+)FIDL",
+                      std::move(experimental_flags));
+
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrLayoutCannotBeParameterized);
+}
+
+TEST(NewSyntaxTests, BadUnsupportedAnonymousLayoutConstraint) {
+  fidl::ExperimentalFlags experimental_flags;
+  experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
+
+  TestLibrary library(R"FIDL(
+library example;
+
+type Foo = struct {
+  foo struct {}:optional;
+};
+)FIDL",
+                      std::move(experimental_flags));
+
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrCannotConstrainInLayoutDecl);
+}
+
+TEST(NewSyntaxTests, BadConstrainTwice) {
+  fidl::ExperimentalFlags experimental_flags;
+  experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
+
+  auto library = WithLibraryZx(R"FIDL(
+library example;
+
+using zx;
+
+alias MyVmo = zx.handle:VMO;
+
+type Foo = struct {
+    foo MyVmo:CHANNEL;
+};
+
+)FIDL",
+                               std::move(experimental_flags));
+
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrCannotConstrainTwice);
+}
+
+TEST(NewSyntaxTests, GoodNoOverlappingConstraints) {
+  fidl::ExperimentalFlags experimental_flags;
+  experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
+
+  auto library = WithLibraryZx(R"FIDL(
+library example;
+
+using zx;
+
+alias MyVmo = zx.handle:<VMO, zx.rights.TRANSFER>;
+
+type Foo = resource struct {
+    foo MyVmo:optional;
+};
+
+)FIDL",
+                               std::move(experimental_flags));
+
+  ASSERT_COMPILED(library);
+}
+
+TEST(NewSyntaxTests, BadWantTypeLayoutParameter) {
+  fidl::ExperimentalFlags experimental_flags;
+  experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
+
+  TestLibrary library(R"FIDL(
+library example;
+
+type Foo = struct {
+    foo vector<3>;
+};
+)FIDL",
+                      std::move(experimental_flags));
+
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrExpectedType);
+}
+
+TEST(NewSyntaxTests, BadWantValueLayoutParameter) {
+  fidl::ExperimentalFlags experimental_flags;
+  experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
+
+  TestLibrary library(R"FIDL(
+library example;
+
+type Foo = struct {
+    foo array<uint8, uint8>;
+};
+)FIDL",
+                      std::move(experimental_flags));
+
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrExpectedValueButGotType);
+}
+
+TEST(NewSyntaxTests, BadShadowedOptional) {
+  fidl::ExperimentalFlags experimental_flags;
+  experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
+
+  TestLibrary library(R"FIDL(
+library example;
+
+const optional uint8 = 3;
+
+type Foo = resource struct {
+    foo vector<uint8>:<10, optional>;
+};
+)FIDL",
+                      std::move(experimental_flags));
+
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrUnexpectedConstraint);
+}
+
+TEST(NewSyntaxTests, BadWrongConstraintType) {
+  fidl::ExperimentalFlags experimental_flags;
+  experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
+
+  TestLibrary library(R"FIDL(
+library example;
+
+type Foo = resource struct {
+    foo vector<uint8>:"hello";
+};
+)FIDL",
+                      std::move(experimental_flags));
+
+  ASSERT_ERRORED_TWICE_DURING_COMPILE(library, fidl::ErrConstantCannotBeInterpretedAsType,
+                                      fidl::ErrUnexpectedConstraint);
+}
+
 }  // namespace
diff --git a/zircon/system/utest/fidl-compiler/resource_tests.cc b/zircon/system/utest/fidl-compiler/resource_tests.cc
index 88d36f2..2e1f0e9 100644
--- a/zircon/system/utest/fidl-compiler/resource_tests.cc
+++ b/zircon/system/utest/fidl-compiler/resource_tests.cc
@@ -35,11 +35,11 @@
   ASSERT_NOT_NULL(resource);
 
   ASSERT_EQ(resource->properties.size(), 1u);
-  EXPECT_EQ(resource->properties[0].type_ctor->name.span()->data(), "MyEnum");
+  EXPECT_EQ(fidl::flat::GetName(resource->properties[0].type_ctor).span()->data(), "MyEnum");
   EXPECT_EQ(resource->properties[0].name.data(), "subtype");
 
-  ASSERT_NOT_NULL(resource->subtype_ctor);
-  EXPECT_EQ(resource->subtype_ctor->name.span()->data(), "uint32");
+  ASSERT_TRUE(fidl::flat::IsTypeConstructorDefined(resource->subtype_ctor));
+  EXPECT_EQ(fidl::flat::GetName(resource->subtype_ctor).span()->data(), "uint32");
 }
 
 TEST(ResourceTests, BadEmpty) {
diff --git a/zircon/system/utest/fidl-compiler/service_tests.cc b/zircon/system/utest/fidl-compiler/service_tests.cc
index e219befb..f261fef 100644
--- a/zircon/system/utest/fidl-compiler/service_tests.cc
+++ b/zircon/system/utest/fidl-compiler/service_tests.cc
@@ -51,13 +51,16 @@
   EXPECT_EQ(service->members.size(), 3);
   const auto& member0 = service->members[0];
   EXPECT_STR_EQ(std::string(member0.name.data()).c_str(), "some_protocol_first_first");
-  EXPECT_STR_EQ(fidl::NameFlatName(member0.type_ctor->name).c_str(), "example/SomeProtocol1");
+  EXPECT_STR_EQ(fidl::NameFlatName(fidl::flat::GetName(member0.type_ctor)).c_str(),
+                "example/SomeProtocol1");
   const auto& member1 = service->members[1];
   EXPECT_STR_EQ(std::string(member1.name.data()).c_str(), "some_protocol_first_second");
-  EXPECT_STR_EQ(fidl::NameFlatName(member1.type_ctor->name).c_str(), "example/SomeProtocol1");
+  EXPECT_STR_EQ(fidl::NameFlatName(fidl::flat::GetName(member1.type_ctor)).c_str(),
+                "example/SomeProtocol1");
   const auto& member2 = service->members[2];
   EXPECT_STR_EQ(std::string(member2.name.data()).c_str(), "some_protocol_second");
-  EXPECT_STR_EQ(fidl::NameFlatName(member2.type_ctor->name).c_str(), "example/SomeProtocol2");
+  EXPECT_STR_EQ(fidl::NameFlatName(fidl::flat::GetName(member2.type_ctor)).c_str(),
+                "example/SomeProtocol2");
 }
 
 TEST(ServiceTests, BadCannotHaveConflictingMembers) {
diff --git a/zircon/system/utest/fidl-compiler/table_tests.cc b/zircon/system/utest/fidl-compiler/table_tests.cc
index 9cc11e4..24d7534 100644
--- a/zircon/system/utest/fidl-compiler/table_tests.cc
+++ b/zircon/system/utest/fidl-compiler/table_tests.cc
@@ -256,6 +256,24 @@
   ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrCannotBeNullable);
 }
 
+TEST(TableTests, BadMultipleConstraints) {
+  fidl::ExperimentalFlags experimental_flags;
+  experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
+  TestLibrary library(R"FIDL(
+library fidl.test.tables;
+
+type Foo = table {
+    1: t int64;
+};
+
+type OptionalTableContainer = struct {
+    foo Foo:<optional, foo, bar>;
+};
+)FIDL",
+                      experimental_flags);
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrTooManyConstraints);
+}
+
 TEST(TableTests, BadOptionalInUnionOld) {
   TestLibrary library(R"FIDL(
 library fidl.test.tables;
@@ -286,8 +304,7 @@
 };
 )FIDL",
                       std::move(experimental_flags));
-  // NOTE(fxbug.dev/72924): same error is used for tables and unions
-  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrNullableOrdinaledMember);
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrCannotBeNullable);
 }
 
 TEST(TableTests, GoodTableInTable) {
@@ -327,7 +344,7 @@
 library fidl.test.tables;
 
 table Foo {
-    1: int64? t;
+    1: string? t;
 };
 )FIDL");
   ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrNullableTableMember);
@@ -340,12 +357,41 @@
 library fidl.test.tables;
 
 type Foo = table {
+    1: t string:optional;
+};
+)FIDL",
+                      std::move(experimental_flags));
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrNullableTableMember);
+}
+
+// NOTE(fxbug.dev/72924): this pair of tests aims to document a behavior
+// difference between the old and new syntaxes: in the old, we check for
+// ErrNullableTableMember first before determining if the type itself can be
+// nullable. This is not the case in the new syntax (we need to compile the
+// type first to determine if it is nullable).
+TEST(TableTests, BadOptionalNonNullableTableMemberOld) {
+  TestLibrary library(R"FIDL(
+library fidl.test.tables;
+
+table Foo {
+    1: int64? t;
+};
+)FIDL");
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrNullableTableMember);
+}
+
+TEST(TableTests, BadOptionalNonNullableTableMember) {
+  fidl::ExperimentalFlags experimental_flags;
+  experimental_flags.SetFlag(fidl::ExperimentalFlags::Flag::kAllowNewSyntax);
+  TestLibrary library(R"FIDL(
+library fidl.test.tables;
+
+type Foo = table {
     1: t int64:optional;
 };
 )FIDL",
                       std::move(experimental_flags));
-  // NOTE(fxbug.dev/72924): we lose the default specific error in the new syntax.
-  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrNullableOrdinaledMember);
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrCannotBeNullable);
 }
 
 TEST(TableTests, BadDefaultNotAllowedOld) {
diff --git a/zircon/system/utest/fidl-compiler/types_tests.cc b/zircon/system/utest/fidl-compiler/types_tests.cc
index 148c1c9..4ba02b7 100644
--- a/zircon/system/utest/fidl-compiler/types_tests.cc
+++ b/zircon/system/utest/fidl-compiler/types_tests.cc
@@ -17,10 +17,11 @@
 
   auto the_type_name = Name::CreateDerived(library, SourceSpan(), std::string(name));
   const Type* the_type;
+  LayoutInvocation invocation;
   ASSERT_TRUE(typespace->Create(
       LibraryMediator(library), the_type_name, nullptr /* maybe_arg_type */,
       std::optional<Name>() /* handle_subtype_identifier */, nullptr /* handle_rights */,
-      nullptr /* maybe_size */, types::Nullability::kNonnullable, &the_type, nullptr));
+      nullptr /* maybe_size */, types::Nullability::kNonnullable, &the_type, &invocation));
   ASSERT_NOT_NULL(the_type, "%s", name);
   auto the_type_p = static_cast<const PrimitiveType*>(the_type);
   ASSERT_EQ(the_type_p->subtype, subtype, "%s", name);
diff --git a/zircon/system/utest/fidl-compiler/union_tests.cc b/zircon/system/utest/fidl-compiler/union_tests.cc
index eefc588..38a4023 100644
--- a/zircon/system/utest/fidl-compiler/union_tests.cc
+++ b/zircon/system/utest/fidl-compiler/union_tests.cc
@@ -427,8 +427,7 @@
 
 )FIDL",
                       std::move(experimental_flags));
-  // NOTE(fxbug.dev/72924): we get a more general error in the new syntax
-  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrNullableOrdinaledMember);
+  ASSERT_ERRORED_DURING_COMPILE(library, fidl::ErrNullableUnionMember);
 }
 
 TEST(UnionTests, BadNoDirectlyRecursiveUnionsOld) {