What happens
ValidateParameter refuses a top-level Void parameter, but nothing refuses Void as a member type or inside a wrapper. ValidateType's WrapperType arm simply recurses, and its default: arm swallows both Void and None:
// Schema/Models/Schema.Validation.cs:723-729
// Every wrapper resolves to whatever it wraps, so a class named inside a Span,
// Handle, Result or Optional is checked exactly as one named directly is.
case WrapperType wrapper:
ValidateType(issues, wrapper.ElementType, path, element);
break;
default:
break;
Member-level checks (Schema.Validation.cs:102-112) warn only on member.Type is None — Void is not mentioned. CppTypeMapper then maps it unconditionally (Schema.Cpp/CppTypeMapper.cs:48): SchemaTypes.Void => new TypeReference("void").
Failure scenario
| member type |
schema.Validate() |
generated C++ |
Void |
clean |
void x{}; |
Optional<Void> |
clean |
std::optional<void> x{}; |
Span<Void> |
clean |
std::span<void> x{}; |
Optional<None> |
clean |
CppGenerationException |
All four C++ outputs are ill-formed — std::optional<void> and std::span<void> are explicitly not instantiable.
Why it matters
CLAUDE.md states the invariant as:
Schema.Validation.cs enforces the corollaries — no Array, Result or Void parameters, no None anywhere generatable.
Neither half holds below the top level.
Optional<None> at least fails loudly at generation time. Void fails silently, and the broken header only surfaces at the consumer's compiler — pointing at generated code rather than at the schema that produced it.
A Void member is also a plausible editing state rather than a contrived one: the type picker offers Void for return types, and the same picker serves members.
Suggested fix
Report Void at any position other than a function return type (and Result's element), and report None wherever ValidateType descends rather than only at the top of a member or parameter.
The WrapperType arm is the single place both can be caught on the way down, so the fix is one check rather than one per call site.
What happens
ValidateParameterrefuses a top-levelVoidparameter, but nothing refusesVoidas a member type or inside a wrapper.ValidateType'sWrapperTypearm simply recurses, and itsdefault:arm swallows bothVoidandNone:Member-level checks (
Schema.Validation.cs:102-112) warn only onmember.Type is None—Voidis not mentioned.CppTypeMapperthen maps it unconditionally (Schema.Cpp/CppTypeMapper.cs:48):SchemaTypes.Void => new TypeReference("void").Failure scenario
schema.Validate()Voidvoid x{};Optional<Void>std::optional<void> x{};Span<Void>std::span<void> x{};Optional<None>CppGenerationExceptionAll four C++ outputs are ill-formed —
std::optional<void>andstd::span<void>are explicitly not instantiable.Why it matters
CLAUDE.md states the invariant as:
Neither half holds below the top level.
Optional<None>at least fails loudly at generation time.Voidfails silently, and the broken header only surfaces at the consumer's compiler — pointing at generated code rather than at the schema that produced it.A
Voidmember is also a plausible editing state rather than a contrived one: the type picker offersVoidfor return types, and the same picker serves members.Suggested fix
Report
Voidat any position other than a function return type (andResult's element), and reportNonewhereverValidateTypedescends rather than only at the top of a member or parameter.The
WrapperTypearm is the single place both can be caught on the way down, so the fix is one check rather than one per call site.