Files
whetstone_DSL/editor/src/AnnotationValidatorExtended.h
Bill 87615a7187 Steps 297-302: Phase 11b complete + Phase 11c step 302 (68/68 tests)
Phase 11b — Validation & Conflict Completion:
- Step 297: Subject 2-4 validation rules E0600-E0805 (12/12)
- Step 298: Subject 5-8 validation rules E0900-E1202 (12/12)
- Step 299: Cross-type conflict detection, 6 conflict pairs (12/12)
- Step 300: TransformEngineExtended — float folding, dead var, annotation-aware (12/12)
- Step 301: Phase 11b integration tests (8/8)

Phase 11c — Parser Deepening (start):
- Step 302: New AST nodes — ClassDeclaration, InterfaceDeclaration,
  MethodDeclaration, GenericType, TypeParameter with JSON roundtrip (12/12)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 16:53:23 +00:00

350 lines
16 KiB
C++

#pragma once
// Steps 297-298: Extended annotation validation for all annotation types
//
// AnnotationValidatorExtended: validates ALL annotation types from Subject 2-8.
// Diagnostic codes E0600-E1202.
//
// The base AnnotationValidator handles memory annotations (E01xx-E05xx).
// This class adds validation for type system, concurrency, scope, shim,
// optimization, meta-programming, and policy annotations.
#include <string>
#include <vector>
#include <initializer_list>
#include "ast/ASTNode.h"
#include "ast/Annotation.h"
#include "AnnotationValidator.h"
class AnnotationValidatorExtended {
public:
using Diagnostic = AnnotationValidator::Diagnostic;
std::vector<Diagnostic> validate(const ASTNode* root) const {
std::vector<Diagnostic> diags;
validateNode(root, diags);
return diags;
}
private:
static bool isPowerOf2(int v) { return v > 0 && (v & (v - 1)) == 0; }
static bool isOneOf(const std::string& val, std::initializer_list<const char*> options) {
for (auto* opt : options) if (val == opt) return true;
return false;
}
void validateNode(const ASTNode* node, std::vector<Diagnostic>& diags) const {
if (!node) return;
for (const auto* anno : node->getChildren("annotations")) {
validateAnnotation(node, anno, diags);
}
for (auto* child : node->allChildren()) {
validateNode(child, diags);
}
}
void validateAnnotation(const ASTNode* host, const ASTNode* anno,
std::vector<Diagnostic>& diags) const {
const auto& ct = anno->conceptType;
// === Subject 2: Type System Annotations ===
if (ct == "BitWidthAnnotation") {
auto* a = static_cast<const BitWidthAnnotation*>(anno);
if (!isPowerOf2(a->width))
diags.push_back({"error",
"[E0600] BitWidth must be power of 2, got " + std::to_string(a->width),
host->id});
}
else if (ct == "EndianAnnotation") {
auto* a = static_cast<const EndianAnnotation*>(anno);
if (!isOneOf(a->order, {"big", "little"}))
diags.push_back({"error",
"[E0601] Endian order must be 'big' or 'little', got '" + a->order + "'",
host->id});
}
else if (ct == "LayoutAnnotation") {
auto* a = static_cast<const LayoutAnnotation*>(anno);
if (!isOneOf(a->mode, {"packed", "aligned"}))
diags.push_back({"error",
"[E0602] Layout mode must be 'packed' or 'aligned', got '" + a->mode + "'",
host->id});
if (a->alignment != 0 && !isPowerOf2(a->alignment))
diags.push_back({"error",
"[E0603] Layout alignment must be positive power of 2, got " + std::to_string(a->alignment),
host->id});
}
else if (ct == "NullabilityAnnotation") {
auto* a = static_cast<const NullabilityAnnotation*>(anno);
if (!isOneOf(a->strategy, {"strict", "nullable"}))
diags.push_back({"error",
"[E0604] Nullability strategy must be 'strict' or 'nullable', got '" + a->strategy + "'",
host->id});
}
else if (ct == "VarianceAnnotation") {
auto* a = static_cast<const VarianceAnnotation*>(anno);
if (!isOneOf(a->variance, {"covariant", "contravariant", "invariant"}))
diags.push_back({"error",
"[E0605] Variance must be 'covariant', 'contravariant', or 'invariant', got '" + a->variance + "'",
host->id});
}
else if (ct == "IdentityAnnotation") {
auto* a = static_cast<const IdentityAnnotation*>(anno);
if (!isOneOf(a->mode, {"nominal", "structural"}))
diags.push_back({"error",
"[E0606] Identity mode must be 'nominal' or 'structural', got '" + a->mode + "'",
host->id});
}
else if (ct == "MutAnnotation") {
auto* a = static_cast<const MutAnnotation*>(anno);
if (!isOneOf(a->depth, {"shallow", "deep", "interior"}))
diags.push_back({"error",
"[E0607] Mut depth must be 'shallow', 'deep', or 'interior', got '" + a->depth + "'",
host->id});
}
else if (ct == "TypeStateAnnotation") {
auto* a = static_cast<const TypeStateAnnotation*>(anno);
if (!isOneOf(a->state, {"erased", "reified"}))
diags.push_back({"error",
"[E0608] TypeState must be 'erased' or 'reified', got '" + a->state + "'",
host->id});
}
// === Subject 3: Concurrency Annotations ===
else if (ct == "AtomicAnnotation") {
auto* a = static_cast<const AtomicAnnotation*>(anno);
if (!isOneOf(a->consistency, {"seq_cst", "relaxed", "acquire", "release"}))
diags.push_back({"error",
"[E0700] Atomic consistency must be 'seq_cst', 'relaxed', 'acquire', or 'release', got '" + a->consistency + "'",
host->id});
}
else if (ct == "SyncAnnotation") {
auto* a = static_cast<const SyncAnnotation*>(anno);
if (!isOneOf(a->primitive, {"monitor", "spin", "semaphore"}))
diags.push_back({"error",
"[E0701] Sync primitive must be 'monitor', 'spin', or 'semaphore', got '" + a->primitive + "'",
host->id});
}
else if (ct == "ThreadModelAnnotation") {
auto* a = static_cast<const ThreadModelAnnotation*>(anno);
if (!isOneOf(a->model, {"green", "os", "fiber"}))
diags.push_back({"error",
"[E0702] ThreadModel must be 'green', 'os', or 'fiber', got '" + a->model + "'",
host->id});
}
else if (ct == "ExecAnnotation") {
auto* a = static_cast<const ExecAnnotation*>(anno);
if (!isOneOf(a->mode, {"async", "event"}))
diags.push_back({"error",
"[E0703] Exec mode must be 'async' or 'event', got '" + a->mode + "'",
host->id});
// E0704: async mode needs ThreadModel on enclosing scope
if (a->mode == "async") {
if (!hasThreadModelOnAncestor(host))
diags.push_back({"error",
"[E0704] Exec mode 'async' requires ThreadModel annotation on enclosing scope",
host->id});
}
}
else if (ct == "BlockingAnnotation") {
auto* a = static_cast<const BlockingAnnotation*>(anno);
if (!isOneOf(a->kind, {"io", "compute"}))
diags.push_back({"error",
"[E0705] Blocking kind must be 'io' or 'compute', got '" + a->kind + "'",
host->id});
}
else if (ct == "ParallelAnnotation") {
auto* a = static_cast<const ParallelAnnotation*>(anno);
if (!isOneOf(a->kind, {"data", "task"}))
diags.push_back({"error",
"[E0706] Parallel kind must be 'data' or 'task', got '" + a->kind + "'",
host->id});
}
else if (ct == "ExceptionAnnotation") {
auto* a = static_cast<const ExceptionAnnotation*>(anno);
if (!isOneOf(a->style, {"checked", "unchecked"}))
diags.push_back({"error",
"[E0707] Exception style must be 'checked' or 'unchecked', got '" + a->style + "'",
host->id});
}
else if (ct == "PanicAnnotation") {
auto* a = static_cast<const PanicAnnotation*>(anno);
if (!isOneOf(a->behavior, {"abort", "unwind"}))
diags.push_back({"error",
"[E0708] Panic behavior must be 'abort' or 'unwind', got '" + a->behavior + "'",
host->id});
}
// === Subject 4: Scope & Namespace Annotations ===
else if (ct == "BindingAnnotation") {
auto* a = static_cast<const BindingAnnotation*>(anno);
if (!isOneOf(a->time, {"static", "dynamic"}))
diags.push_back({"error",
"[E0800] Binding time must be 'static' or 'dynamic', got '" + a->time + "'",
host->id});
}
else if (ct == "LookupAnnotation") {
auto* a = static_cast<const LookupAnnotation*>(anno);
if (!isOneOf(a->mode, {"lexical", "hoisted"}))
diags.push_back({"error",
"[E0801] Lookup mode must be 'lexical' or 'hoisted', got '" + a->mode + "'",
host->id});
}
else if (ct == "CaptureAnnotation") {
auto* a = static_cast<const CaptureAnnotation*>(anno);
if (!isOneOf(a->strategy, {"value", "ref", "move"}))
diags.push_back({"error",
"[E0802] Capture strategy must be 'value', 'ref', or 'move', got '" + a->strategy + "'",
host->id});
}
else if (ct == "VisibilityAnnotation") {
auto* a = static_cast<const VisibilityAnnotation*>(anno);
if (!isOneOf(a->level, {"private", "internal", "friend", "public"}))
diags.push_back({"error",
"[E0803] Visibility level must be 'private', 'internal', 'friend', or 'public', got '" + a->level + "'",
host->id});
}
else if (ct == "NamespaceAnnotation") {
auto* a = static_cast<const NamespaceAnnotation*>(anno);
if (!isOneOf(a->style, {"qualified", "flat"}))
diags.push_back({"error",
"[E0804] Namespace style must be 'qualified' or 'flat', got '" + a->style + "'",
host->id});
}
else if (ct == "ScopeAnnotation") {
auto* a = static_cast<const ScopeAnnotation*>(anno);
if (!isOneOf(a->kind, {"local", "global_leaked", "singleton"}))
diags.push_back({"error",
"[E0805] Scope kind must be 'local', 'global_leaked', or 'singleton', got '" + a->kind + "'",
host->id});
}
// === Subject 5: Shim & Escape Hatch Annotations ===
else if (ct == "CallingConvAnnotation") {
auto* a = static_cast<const CallingConvAnnotation*>(anno);
if (!isOneOf(a->convention, {"stdcall", "cdecl", "fastcall"}))
diags.push_back({"error",
"[E0900] CallingConv convention must be 'stdcall', 'cdecl', or 'fastcall', got '" + a->convention + "'",
host->id});
}
else if (ct == "ShimAnnotation") {
auto* a = static_cast<const ShimAnnotation*>(anno);
if (!isOneOf(a->strategy, {"vtable", "trampoline", "union_tag", "cast"}))
diags.push_back({"error",
"[E0901] Shim strategy must be 'vtable', 'trampoline', 'union_tag', or 'cast', got '" + a->strategy + "'",
host->id});
}
// === Subject 6: Optimization Annotations ===
else if (ct == "InlineAnnotation") {
// E1000: Inline(Always) + TailCall conflict
auto* a = static_cast<const InlineAnnotation*>(anno);
if (a->mode == "Always") {
for (const auto* sibling : host->getChildren("annotations")) {
if (sibling->conceptType == "TailCallAnnotation") {
diags.push_back({"error",
"[E1000] Inline(Always) conflicts with TailCall annotation",
host->id});
break;
}
}
}
}
else if (ct == "LoopAnnotation") {
auto* a = static_cast<const LoopAnnotation*>(anno);
if (!isOneOf(a->hint, {"unroll", "vectorize", "fuse"}))
diags.push_back({"error",
"[E1001] Loop hint must be 'unroll', 'vectorize', or 'fuse', got '" + a->hint + "'",
host->id});
if (a->factor < 0 || (a->factor == 0 && a->hint == "unroll"))
diags.push_back({"error",
"[E1002] Loop factor must be positive, got " + std::to_string(a->factor),
host->id});
}
else if (ct == "AlignAnnotation") {
auto* a = static_cast<const AlignAnnotation*>(anno);
if (!isPowerOf2(a->bytes))
diags.push_back({"error",
"[E1003] Align bytes must be positive power of 2, got " + std::to_string(a->bytes),
host->id});
}
else if (ct == "OverflowAnnotation") {
auto* a = static_cast<const OverflowAnnotation*>(anno);
if (!isOneOf(a->behavior, {"wrap", "saturation", "panic"}))
diags.push_back({"error",
"[E1004] Overflow behavior must be 'wrap', 'saturation', or 'panic', got '" + a->behavior + "'",
host->id});
}
// === Subject 7: Meta-Programming Annotations ===
else if (ct == "MetaAnnotation") {
auto* a = static_cast<const MetaAnnotation*>(anno);
if (!isOneOf(a->state, {"quoted", "unquoted"}))
diags.push_back({"error",
"[E1100] Meta state must be 'quoted' or 'unquoted', got '" + a->state + "'",
host->id});
if (!isOneOf(a->phase, {"compile", "runtime"}))
diags.push_back({"error",
"[E1101] Meta phase must be 'compile' or 'runtime', got '" + a->phase + "'",
host->id});
}
else if (ct == "SymbolAnnotation") {
auto* a = static_cast<const SymbolAnnotation*>(anno);
if (!isOneOf(a->mode, {"gensym", "interned"}))
diags.push_back({"error",
"[E1102] Symbol mode must be 'gensym' or 'interned', got '" + a->mode + "'",
host->id});
}
else if (ct == "EvaluateAnnotation") {
auto* a = static_cast<const EvaluateAnnotation*>(anno);
if (!isOneOf(a->phase, {"compile_time", "runtime"}))
diags.push_back({"error",
"[E1103] Evaluate phase must be 'compile_time' or 'runtime', got '" + a->phase + "'",
host->id});
}
else if (ct == "TemplateAnnotation") {
auto* a = static_cast<const TemplateAnnotation*>(anno);
if (!isOneOf(a->specialization, {"trait", "monomorphize", "erasure"}))
diags.push_back({"error",
"[E1104] Template specialization must be 'trait', 'monomorphize', or 'erasure', got '" + a->specialization + "'",
host->id});
}
// === Subject 8: Policy Annotations ===
else if (ct == "PolicyAnnotation") {
auto* a = static_cast<const PolicyAnnotation*>(anno);
if (!a->strictness.empty() && !isOneOf(a->strictness, {"high", "low"}))
diags.push_back({"error",
"[E1200] Policy strictness must be 'high' or 'low', got '" + a->strictness + "'",
host->id});
if (!a->perf.empty() && !isOneOf(a->perf, {"critical", "normal"}))
diags.push_back({"error",
"[E1201] Policy perf must be 'critical' or 'normal', got '" + a->perf + "'",
host->id});
if (!a->style.empty() && !isOneOf(a->style, {"idiomatic", "literal"}))
diags.push_back({"error",
"[E1202] Policy style must be 'idiomatic' or 'literal', got '" + a->style + "'",
host->id});
}
}
// Walk ancestors looking for a ThreadModelAnnotation
bool hasThreadModelOnAncestor(const ASTNode* node) const {
const ASTNode* cur = node->parent;
while (cur) {
for (const auto* anno : cur->getChildren("annotations")) {
if (anno->conceptType == "ThreadModelAnnotation")
return true;
}
cur = cur->parent;
}
return false;
}
};