07 — D-Implementierung: Tag-Subsystem in der Type Engine¶
Status: Implementierungsentwurf für Bootstrap-Compiler in D
1. Entscheidung¶
Tags sind compile-time-only. Sie gehören in die Type Engine. Sie brauchen keine RuntimeBus-Schnittstelle. Sie brauchen aber eigene CompilerDb-Queries.
CompilerDb -> compile-time, deterministische Queries
RuntimeBus -> runtime, side-effect Commands
Tags -> CompilerDb
Tags -> kein RuntimeBus
2. Kein TypeId-Sonderfall¶
Nicht empfohlen:
Das macht Tags zu Pseudo-Types und lädt später zu internen Sonderfällen ein. Diese Sorte Einladung nimmt schlechte Architektur immer begeistert an.
Empfohlen:
Separate ID-Spaces oder mindestens stark typisierte Wrapper.
3. Symbolarten¶
Symbol:
struct Symbol {
SymbolId id;
SymbolKind kind;
union Payload {
TypeId typeId;
TagId tagId;
ProtocolId protocolId;
ServiceId serviceId;
TypeFnId typeFnId;
FunctionId functionId;
ModuleId moduleId;
}
Payload payload;
}
4. TagEngine¶
Die TagEngine ist ein interner Dienst der Type Engine.
Descriptors:
enum TagKind {
plain,
enumDomain,
enumVariant,
}
struct TagDescriptor {
TagId id;
SymbolId symbol;
TagKind kind;
MetaRecordId meta;
SourceSpan declaredAt;
}
struct TagEnumDomain {
TagId id;
TagId[] variants;
}
struct TagEnumVariant {
TagId id;
TagId domain;
}
Beispiele:
SharePolicy -> TagKind.enumDomain
Local -> TagKind.enumVariant, domain = SharePolicy
Send -> TagKind.enumVariant, domain = SharePolicy
Sync -> TagKind.enumVariant, domain = SharePolicy
5. Type-Level-Werte¶
TypeFn-Auswertung benötigt eine gemeinsame Repräsentation für Type-Level-Werte.
enum TypeLevelValueKind {
type_,
tag,
metaRecord,
protocol,
service,
literal,
labelPredicate,
}
struct TypeLevelValue {
TypeLevelValueKind kind;
union Payload {
TypeId typeId;
TagId tagId;
MetaRecordId metaId;
ProtocolId protocolId;
ServiceId serviceId;
LiteralValue literal;
LabelPredicate label;
}
Payload payload;
}
Damit kann typefn Share(policy: SharePolicy) sauber prüfen:
6. CompilerDb Queries¶
Minimaler Query-Satz:
struct ResolveTag {
ScopeId scope;
SymbolName name;
}
// -> TagId
struct GetTagDescriptor {
TagId id;
}
// -> TagDescriptor
struct IsTagInDomain {
TagId tag;
TagId domain;
}
// -> bool
struct GetTagMeta {
TagId id;
}
// -> MetaRecord
struct NormalizeAppliedTag {
TagId id;
}
// -> MetaRecord
Später:
struct GetTagVariants {
TagId domain;
}
// -> TagId[]
struct ResolveTagVariant {
TagId domain;
SymbolName name;
}
// -> TagId
struct GetTagFingerprint {
TagId id;
}
// -> Fingerprint
struct CheckTagCycles {
ModuleId module_;
}
// -> Diagnostic[]
7. Query-Abhängigkeiten¶
Wenn ein Typ TaskLocal anwendet:
dann hängt seine Meta-Normalisierung ab von:
Ändert sich TaskLocal, muss nur davon abhängige Meta-Normalisierung und Label-Closure invalidiert werden.
Genau dafür ist CompilerDb da. Sonst darf der Compiler wieder in den Kaffeesatz greifen. Eher unpräzise, auch für Kaffee.
8. Keine RuntimeBus-Integration¶
Tags haben:
- keine Runtime-Werte
- kein Runtime-Layout
- keine VM-Operationen
- keine Intrinsics
- keine RuntimeBus-Commands
RuntimeBus bleibt für runtime side effects:
Tags sind TypeEngine-Daten.
9. Artifact- und Plugin-Relevanz¶
Tags müssen in Artefakten als TypeEngine-Metadaten exportierbar sein.
Für Varianten:
Tag-Domains sind nominal. Zwei Tags mit gleichem Namen aus verschiedenen Packages sind verschieden.
10. Invarianten¶
D-1: Jeder TagId referenziert genau einen TagDescriptor.
D-2: Jeder enumVariant-Tag gehört genau zu einer enumDomain.
D-3: Eine Tag-Domain enthält keine doppelten Variantennamen.
D-4: Tags besitzen kein Runtime-Layout.
D-5: Tags können keine Protocol-Implementierungen besitzen.
D-6: Tag-Meta muss normalisierbar sein.
D-7: Tag-Meta-Zyklen werden als Diagnostic gemeldet.
D-8: Andere Subsysteme greifen nie direkt auf TagEngine-Tabellen zu.
D-9: Zugriff erfolgt über CompilerDb Queries.
D-10: Tags erzeugen keine RuntimeBus-Commands.