JDL — Compiler-Annotationen (@...)¶
Status: Draft
Geltungsbereich: Frontend, Compiler-Pipeline, Tooling, Runtime-Bindung
Abhängigkeiten: 00-refinements.md, 13-ir-spec.md, jade-compiler-db-spec.md, 08-intrinsics-vm-api.md
Nicht-Ziele: User-Makros, allgemeines Metaprogramming, Runtime-Code-Rewriting
0. Zweck, Scope und Nicht-Ziele¶
Dieses Dokument definiert die Compiler-Annotationen von JDL.
Compiler-Annotationen sind eine kleine, deterministische Sonderoberfläche
zwischen JDL und Jade. Sie existieren ausschließlich für Dinge, die:
- der Compiler zwingend wissen muss,
- deterministische Build- oder Runtime-Verträge ausdrücken,
- statische Registrierung für Tooling oder Runtime-Hooks bereitstellen,
- oder frühe Compile-Time-Entscheidungen ermöglichen, die nicht natürlich in das normale Typ- und Refinement-System fallen.
Compiler-Annotationen sind nicht:
- ein allgemeines Makro-System,
- eine zweite Sprachebene,
- ein Ersatz für das Typ-Metasystem,
- ein Mittel zur Laufzeit-Umschreibung von Programmen.
Alles domänenspezifische Meta gehört in:
- Meta-Records (
<{ ... }>), - Refinements (
:>), - TypeFns,
- Protocols /
provide, - Generators / Collectors,
- oder normale JDL-Abstraktionen.
1. Grundbegriffe¶
1.1 Compiler-Annotation¶
Eine Compiler-Annotation ist ein mit @ eingeleiteter, compilerseitig
bekannter Marker oder Vertrag mit fest definierter Semantik.
Beispiele:
1.2 Pruning¶
Pruning ist das vollständige Entfernen von AST-Knoten vor Name Resolution, Type Checking und IR-Building.
1.3 Validation¶
Validation ist eine Compile-Time-Prüfung, die bei Verletzung einen Compilerfehler erzeugt, aber keinen Code umschreibt.
1.4 Diagnose-Annotation¶
Eine Annotation, deren einzige Wirkung diagnostisch ist, z. B. Warnungen bei Benutzung eines Symbols.
1.5 Registry-Annotation¶
Eine Annotation, die Symbole oder Funktionen für Tooling, Testsysteme oder Runtime-Bindung deterministisch registrierbar macht.
1.6 Runtime-Hook¶
Ein Symbol mit spezieller, vom Compiler oder der Runtime erwarteter Rolle,
z. B. main oder ein Panic-Handler.
1.7 Build-Kontext¶
Der Build-Kontext ist die feste Menge compile-time bekannter Fakten, gegen
die @cfg und frühe @assert-Prädikate ausgewertet werden.
Beispiele:
target.ostarget.archbuild.modefeature("ffi")
1.8 Link-Unit¶
Eine Link-Unit ist die Menge von Modulen / Artefakten, die gemeinsam zu einem ausführbaren Programm oder einer Bibliothek verbunden werden.
2. Allgemeine Invarianten¶
Die folgenden Regeln gelten für alle Compiler-Annotationen.
2.1 Determinismus¶
Compiler-Annotationen müssen deterministisch auswertbar sein.
Gleicher Quelltext + gleicher Build-Kontext = gleiche Annotationsergebnisse.
2.2 Keine Laufzeit-Umschreibung¶
Compiler-Annotationen führen keine dynamische Laufzeit-Umschreibung ein. Sie erzeugen keine „Meta-Logik“, die sich erst bei Programmausführung entfaltet.
2.3 Keine zweite Sprache¶
Compiler-Annotationen etablieren keine zweite, frei programmierbare Sprache innerhalb von JDL.
Es gibt:
- keine user-definierten Compiler-Annotationen mit eigener Semantik,
- kein allgemeines Makro-System,
- kein unkontrolliertes AST-Rewriting durch Benutzer.
2.4 Typ-Meta hat Vorrang für Domänenlogik¶
Wenn ein Problem natürlich durch Meta-Records, TypeFns, Refinements oder Protocols ausdrückbar ist, soll es nicht als Compiler-Annotation modelliert werden.
2.5 Unbekannte Annotationen¶
Eine unbekannte Compiler-Annotation ist ein Compilerfehler, sofern dieses Dokument oder eine spätere normative Ergänzung nicht explizit etwas anderes festlegt.
2.6 Keine semantische Schleichmagie¶
Eine Compiler-Annotation darf beobachtbares Programmverhalten nicht heimlich ändern, außer dies ist ihre explizit definierte Aufgabe und normativ beschrieben.
2.7 Klare Phasenzuordnung¶
Jede Compiler-Annotation muss genau angeben:
- in welcher Compiler-Phase sie ausgewertet wird,
- welche Eingaben sie lesen darf,
- und welche Art von Wirkung sie erzeugt.
3. Pipeline-Phasen¶
Compiler-Annotationen wirken nicht alle zur selben Zeit. Ihre Semantik ist an die Compiler-Pipeline gebunden.
3.1 Phasenübersicht¶
| Phase | Beschreibung |
|---|---|
| Parse | Quelltext wird in einen AST mit Annotation-Knoten geparst |
| Prune | frühe AST-Reduktion vor Name Resolution |
| Resolve | Name Resolution |
| Typecheck | Typprüfung |
| Layout | Typ-/Layoutinformationen vollständig verfügbar |
| Registry | Sammlung von Test-/Hook-/Lang-Items |
| IR Build | IR-Builder konsumiert bereits bereinigten AST |
| Lowering / Codegen | Annotationen wirken hier nur noch indirekt |
3.2 Phase: Parse¶
Bei Parse werden Annotationen als AST-Bestandteile erfasst. Ihre Präsenz allein hat noch keine Wirkung.
3.3 Phase: Prune¶
In der Prune-Phase werden Annotationen ausgewertet, die AST-Knoten vollständig entfernen können.
Normativ: @cfg wirkt in dieser Phase.
3.4 Phase: Typecheck / Layout¶
Annotationen, die Typ- oder Layoutwissen benötigen, dürfen erst ausgewertet werden, wenn die benötigten Informationen vollständig vorliegen.
Normativ:
@assert(early) wirkt vor oder unabhängig vom Typecheck,@assert_typewirkt nach vollständiger Typ-/Layoutauflösung.
3.5 Phase: Registry¶
Registry-Annotationen sammeln deterministisch Symbole, ohne deren normale Sprachsemantik zu verändern.
Normativ:
@test@lang_item
wirken in dieser Phase.
3.6 Beziehung zur IR-Pipeline¶
Der IR-Builder sieht nur den AST nach Pruning und nach den früh wirksamen Validation-Schritten.
Insbesondere:
- durch
@cfgentfernte Zweige existieren für Resolution, Typecheck und IR-Building nicht, - erfolgreiche
@assert/@assert_typeverändern den AST nicht, - Diagnose-Annotationen verändern den IR nicht.
4. Zulässige Annotation-Familien¶
Nur die in diesem Dokument beschriebenen Annotation-Familien sind Teil des Compiler-Annotationssystems.
4.1 @cfg(...)¶
4.1.1 Zweck¶
@cfg(...) aktiviert oder deaktiviert AST-Teile abhängig vom Build-Kontext.
4.1.2 Zulässige Positionen¶
Erlaubt:
- auf Items,
- auf Blöcken, die ausschließlich aus Items bestehen.
Nicht erlaubt:
- auf beliebigen Ausdrücken,
- mitten in normalen Expressions,
- als allgemeiner Laufzeit-Branch-Ersatz.
4.1.3 Auswertungsphase¶
@cfg wird nach Parse und vor Name Resolution ausgewertet.
4.1.4 Semantik¶
Wenn das Prädikat true ist, bleibt der annotierte Knoten erhalten.
Wenn das Prädikat false ist, wird der annotierte Knoten vollständig aus
dem AST entfernt.
4.1.5 Konsequenzen¶
Inaktive @cfg-Zweige:
- müssen nicht typkorrekt sein,
- nehmen nicht an Name Resolution teil,
- erzeugen keinen IR.
4.1.6 Beispiele¶
@cfg(target.os == "linux")
pub def useEpoll() -> Result[(), IoError] = ...
@cfg(feature("test")) {
@test
def storage_roundtrip() -> Result[(), TestError] = ...
}
4.1.7 Fehlerfälle¶
Compilerfehler bei:
- ungültigem Prädikat,
- unzulässiger Position,
- unbekannten Build-Kontext-Schlüsseln,
- unbekannten Features.
4.2 @assert(...)¶
4.2.1 Zweck¶
@assert(...) erzwingt frühe, compile-time auswertbare Bedingungen, ohne
Code umzuschreiben.
4.2.2 Auswertungsphase¶
@assert wird nach @cfg-Pruning ausgewertet.
4.2.3 Zulässige Eingaben¶
Erlaubt sind Prädikate gegen den Build-Kontext und die Predicate-DSL aus Abschnitt 5.
Keine Typ- oder Layout-Queries.
4.2.4 Semantik¶
Wenn das Prädikat true ist, geschieht nichts.
Wenn das Prädikat false ist, erzeugt der Compiler einen Fehler.
4.2.5 Beispiele¶
@assert(target.os == "linux", "This module requires Linux")
pub extern Epoll { ... }
@assert(feature("ffi"), "FFI support must be enabled")
pub def loadNativePlugin(path: str) -> Result[Plugin, PluginError] = ...
4.2.6 Fehlerfälle¶
Compilerfehler bei:
- ungültigem Prädikat,
- unzulässiger Position,
- nicht auswertbarem Ausdruck,
- fehlenden oder falschen Argumenten.
4.3 @assert_type(...)¶
4.3.1 Zweck¶
@assert_type(...) erzwingt Compile-Time-Bedingungen, die vollständige
Typ- oder Layoutinformationen benötigen.
4.3.2 Auswertungsphase¶
@assert_type wird nach vollständiger Typauflösung und Layoutberechnung
ausgewertet.
4.3.3 Zulässige Eingaben¶
Zusätzlich zur Predicate-DSL dürfen type-aware Queries verwendet werden, z. B.:
sizeof(T)alignof(T)layoutof(T)
4.3.4 Semantik¶
Wenn das Prädikat true ist, geschieht nichts.
Wenn das Prädikat false ist, erzeugt der Compiler einen Fehler.
4.3.5 Beispiele¶
@assert_type(sizeof(MyHeader) == 16, "MyHeader must remain ABI-stable")
pub type MyHeader: struct { ... }
@assert_type(alignof(Vec4) == 16, "Vec4 must stay 16-byte aligned")
pub type Vec4: struct { ... }
4.3.6 Fehlerfälle¶
Compilerfehler bei:
- Nutzung vor verfügbarer Typ-/Layoutinformation,
- unzulässigen Queries,
- nicht auswertbaren Bedingungen.
4.4 @deprecated(...)¶
4.4.1 Zweck¶
@deprecated(...) markiert Symbole als veraltet und erzeugt Diagnosemeldungen
bei Verwendung.
4.4.2 Wirkung¶
Die Annotation verändert keine Semantik.
Sie erzeugt eine Warnung bei Verwendung, nicht schon beim bloßen Import.
4.4.3 Zulässige Payload¶
Optional:
- Nachricht,
since,replacement.
4.4.4 Beispiele¶
@deprecated("Use newApi() instead")
pub def oldApi() -> Result[(), ApiError] = ...
@deprecated(message = "Use Effect.run()", since = "0.4", replacement = "Effect.run")
pub def runEffectNow[T](...) -> T = ...
4.4.5 Fehlerfälle¶
Compilerfehler bei ungültiger Payload-Form oder unzulässiger Position.
4.5 @test¶
4.5.1 Zweck¶
@test markiert Funktionen für Test-Discovery und Test-Registry.
4.5.2 Auswertungsphase¶
@test wirkt in der Registry-Phase.
4.5.3 Semantik¶
- Der Compiler oder ein Tool sammelt annotierte Funktionen.
- Die Funktion bleibt normale JDL-Funktion.
- Test-Ausführung erfolgt in einem separaten Test-Modus.
4.5.4 Varianten¶
@test@test.ignore("reason")@test.only
4.5.5 Signaturregeln¶
Die zulässigen Testsignaturen sind vom Testsystem festgelegt.
Empfohlene MVP-Formen:
() -> ()() -> Result[(), E]
4.5.6 Beispiele¶
@test
def parser_roundtrip() -> Result[(), TestError] = ...
@test.ignore("broken on current bridge draft")
def ffi_sqlite_smoke() -> Result[(), TestError] = ...
4.5.7 Fehlerfälle¶
Compilerfehler oder Registry-Fehler bei:
- unzulässiger Signatur,
- ungültiger Payload,
- doppelter oder widersprüchlicher Test-Annotation.
4.6 @lang_item(...)¶
4.6.1 Zweck¶
@lang_item(...) bindet ein Symbol explizit an eine vom Compiler oder der
Runtime erwartete Sonderrolle.
4.6.2 Anwendungsfälle¶
Beispiele:
main- globaler Panic-/Error-Handler
- Default-Storage-/Allocator-Provider
- Modul-Init-/Deinit-Hooks
4.6.3 Auswertungsphase¶
@lang_item wirkt in der Registry- bzw. Link-Phase.
4.6.4 Semantik¶
Ein @lang_item(name) markiert eine Definition als Träger genau dieser Rolle.
Normativ:
- pro Rolle ist höchstens eine gültige Definition pro Link-Unit erlaubt,
- die erwartete Signatur ist fest vorgegeben,
- fehlende Pflicht-Items sind Compiler- oder Link-Fehler,
- optionale Lang-Items dürfen Default-Verhalten haben.
4.6.5 Beispiele¶
@lang_item("main")
def appMain() -> i32 = ...
@lang_item("panic_handler")
def handlePanic(p: PanicInfo) -> Never = ...
4.6.6 Fehlerfälle¶
Compiler- oder Link-Fehler bei:
- doppelter Definition derselben Rolle,
- falscher Signatur,
- unzulässiger Platzierung,
- fehlendem Pflicht-Lang-Item.
4.7 Optionale Hint-Annotationen¶
Die folgenden Annotationen sind optionale, nicht-semantische Hints:
@inline@noinline@lint.allow(...)@lint.deny(...)
4.7.1 Regel¶
Hints dürfen beobachtbares Verhalten nicht verändern.
4.7.2 Status¶
Nicht Teil des minimalen garantierten MVP, sofern nicht separat festgelegt.
5. Predicate-DSL¶
Die Predicate-DSL wird von @cfg(...) und frühem @assert(...) verwendet.
5.1 Ziele¶
Die DSL muss:
- deterministisch,
- klein,
- parserfreundlich,
- und unabhängig von Runtime-Semantik sein.
5.2 Zulässige Elemente¶
Erlaubt:
- Bool-Literale,
- String-Literale,
- Integer-Literale,
==,!=,- optional
<,<=,>,>=für numerische Werte, and,or,notbzw. äquivalente Operatorformen,- Klammerung,
- Build-Kontext-Zugriffe,
feature("name").
5.3 Build-Kontext-Zugriffe¶
Beispiele:
target.ostarget.archbuild.modebuild.debug
Die Menge zulässiger Schlüssel ist compilerseitig festgelegt.
5.4 Feature-Abfrage¶
liefert einen booleschen Wert aus dem Build-Kontext.
5.5 Nicht erlaubt¶
Nicht erlaubt:
- Funktionsaufrufe außerhalb der fest definierten DSL,
- Zugriff auf Runtime-Werte,
- Typqueries in
@cfgoder frühem@assert, - Seiteneffekte.
5.6 Fehlerregeln¶
Unbekannte Schlüssel oder Features sind Compilerfehler, keine stillen false-Werte.
6. Beziehung zu anderen Mechanismen¶
6.1 @ vs. Typ-Meta¶
Compiler-Annotationen sind nicht das normale Meta-System von JDL.
Typ-Meta beschreibt:
- Policies,
- Typkonstruktion,
- Verhalten,
- Constraints,
- kompositorische Sprachsemantik.
Compiler-Annotationen beschreiben dagegen:
- frühe Compilerentscheidungen,
- Diagnose,
- Registry,
- Runtime-Hooks.
6.2 @cfg vs. type-level if¶
@cfg entfernt AST-Knoten vor Resolution/Typecheck.
Ein type-level if ist normale Typsemantik und entfernt keinen AST.
6.3 @ vs. Intrinsics¶
Intrinsics sind compiler- oder VM-provided Funktionen bzw. TypeFns an einer stabilen Sprach-/Runtime-Grenze.
Compiler-Annotationen bleiben AST- bzw. Compiler-Pipeline-Konzepte.
Beispiel:
@cfg(...)= AST-Pruning,labels_of[T]= type-level Intrinsic,jdl.vm_pin(...)= Runtime-Intrinsic.
6.4 @ vs. IR-Desugaring¶
Compiler-Annotationen sind keine IR-Instruktionen.
Sie wirken vor oder neben dem IR-Building und transportieren ihre Wirkung höchstens indirekt in den IR.
6.5 @lang_item vs. normale Sprachbindung¶
@lang_item ist keine Alternative zu provide, service, protocol oder
normaler Modul-Semantik.
Es markiert nur Spezialrollen, die der Compiler oder die Runtime explizit verdrahtet erwarten.
7. Fehlerregeln und Diagnostik¶
7.1 Allgemeine Regel¶
Fehlerhafte Compiler-Annotationen müssen präzise Diagnosen erzeugen.
Die Diagnose soll mindestens enthalten:
- welche Annotation fehlerhaft ist,
- warum sie fehlerhaft ist,
- an welcher Stelle,
- und wenn möglich, was stattdessen zulässig wäre.
7.2 Typische Fehlerklassen¶
- unbekannte Annotation,
- ungültige Position,
- falsche Payload,
- ungültiges oder nicht auswertbares Prädikat,
- mehrfache
@lang_item-Definition, - unzulässige Testsignatur,
- Nutzung einer layoutabhängigen Assertion vor Layout-Phase.
7.3 Diagnoseprinzipien¶
- keine stillen Fallbacks,
- keine implizite Ignorierung unbekannter Annotationen,
- Build-Kontext-Fehler sind hart,
- inaktive
@cfg-Zweige werden nicht typgeprüft und erzeugen daher auch keine Typfehler aus entferntem Code.
8. MVP-Whitelist¶
Die folgende Menge ist Teil des minimalen, stabil anzustrebenden MVP:
@cfg(...)@assert(...)- optional
@assert_type(...) @deprecated(...)@test@test.ignore(...)@test.only@lang_item(...)
Nicht Teil des harten MVP, sofern nicht separat festgelegt:
@mock@inline@noinline@lint.allow(...)@lint.deny(...)
9. Offene Punkte¶
9.1 @lang_item-Scope¶
Offen ist, ob @lang_item-Eindeutigkeit pro Modul, pro Package oder pro
vollständiger Link-Unit gilt.
Empfohlene Richtung: Eindeutigkeit pro Link-Unit.
9.2 Zulässige Build-Kontext-Schlüssel¶
Die exakte normative Liste von:
target.*build.*feature(...)
muss separat fixiert werden.
9.3 Testsignaturen¶
Die endgültig erlaubten Signaturen für @test sind noch festzulegen.
9.4 @assert / @assert_type Trennung¶
Offen ist, ob beide dauerhaft getrennte Annotationen bleiben oder eine spätere, explizit gestufte Einzelform eingeführt wird.
9.5 Lint-Hints¶
Offen ist, ob Lint-Annotationen Teil des Kernsystems oder nur Tooling-Sache werden.
10. Rationale¶
10.1 Warum keine Makros¶
Makros etablieren oft eine zweite, schlechter spezifizierte Sprachebene. Sie erschweren:
- Debugging,
- Tooling,
- Diagnosequalität,
- und semantische Lesbarkeit.
JDL bevorzugt stattdessen:
- normales Typ-Meta,
- TypeFns,
- Protocols,
- kleine Compiler-Annotationen,
- und klar begrenzte Intrinsics.
10.2 Warum @cfg kein Präprozessor sein soll¶
@cfg ist absichtlich auf frühes, strukturelles AST-Pruning begrenzt.
Es soll keine beliebige Quelltext-Magie werden.
10.3 Warum @ klein bleiben muss¶
Je mehr Logik nach @ wandert, desto mehr verliert JDL seine eigene
sprachliche Ausdruckskraft zugunsten einer compilerseitigen Sonderwelt.
Die zentrale Regel lautet daher:
So viel wie möglich in normaler JDL-Semantik.
@nur dort, wo die Sprachgrenze bewusst und deterministisch markiert werden muss. ```