Zum Inhalt

Working Draft: Compiler-Annotations (@...) als deterministische Schnittstelle zwischen JDL und Jade

Status: Working Draft (ohne bindende Wirkung)
Zweck: Ideen-/Architektur-Referenz für Jade/JDL. Syntaxdetails sind ausdrücklich nicht final.


1. Motivation

Jade/JDL braucht eine schmale, präzise definierte „Kante“ zwischen:

  • JDL (User-Code, Semantik, Typen, Effekte) und
  • Jade (VM, Runtime-Subsysteme, Loader/Linker, Diagnostik/Tooling)

Diese Kante soll:

  1. deterministisch sein (keine „Makro-Magie“, kein unkontrollierbares Code-Rewriting),
  2. minimal bleiben (nicht alles ins Typsystem pressen, nur weil es dort theoretisch geht),
  3. effizient sein (Compiler/VM dürfen klare Datenstrukturen erzeugen),
  4. tooling-freundlich sein (Parser/LSP/Indexer verstehen das zuverlässig).

Dafür wird eine kleine Menge Compiler-Annotations eingeführt, die visuell und syntaktisch über @... erkennbar sind.


2. Grundsatzregel: Was bedeutet @?

Wenn du ein @ siehst, siehst du eine Compiler-Annotation.

Compiler-Annotationen sind nicht das Meta-System für Domain- oder Framework-Logik.
Sie sind ausschließlich für Dinge gedacht, die:

  • der Compiler zwingend wissen muss, oder
  • die deterministische Build-/Runtime-Verträge ausdrücken, oder
  • nur Diagnostik/Registrierung für Tooling bereitstellen.

Alles Domain-/App-spezifische Meta gehört ins Typ-Metasystem (Type Descriptors, TypeFns, Refinements, Generatoren/Collector-Mechaniken).


3. Design-Leitplanken (nicht verhandelbar, sonst kippt es)

3.1 Determinismus

@-Annotationen dürfen keine dynamische Programmlogik einführen und keine Laufzeit-Umschreibung durchführen.

Erlaubt sind nur: - AST-Pruning (Entfernen von Code vor Name Resolution/Typecheck), - compile-time Validierung (harte Fehlermeldungen), - Diagnostik (Warnings/Lints), - deterministische Registrierung (z.B. Tests, Runtime-Hooks, Handler-Tabellen), - Backend-Hints, sofern sie Semantik nicht ändern.

3.2 Keine zweite Sprache

Keine User-definierten Compiler-Annotationen mit eigener Semantik.
Wenn User eigene Metadaten brauchen: Typ-Meta.

3.3 Keine Expression-level @cfg im MVP

@cfg wirkt auf Items/Blöcke aus Items.
Keine „überall im Ausdruck“ Bedingungen, sonst wird @ zur Präprozessor-Hölle.


4. Kategorien von Compiler-Annotationen

A) Prune-Annotationen (Frontend, AST-Entfernung)

Ziel: Codezweige eliminieren, bevor Scope/Typen/Codegen überhaupt betroffen sind.

Kernfeature: @cfg(predicate)

Formen (konzeptionell): - Item-Form: @cfg(pred) <item> - Block-Form: @cfg(pred) { <item>* }

Wichtig: Block ist kein neuer Semantik-Scope.
Wenn aktiv, ist der Inhalt so, als stünde er normal im Modul.
Wenn inaktiv, ist er komplett entfernt.

Pruning-Pass-Reihenfolge: 1. Parse (inkl. Annotation-Knoten) 2. cfg_prune gegen festen Build-Kontext 3. erst danach: Name Resolution, Typecheck, Codegen

Damit gilt: inaktive Zweige müssen nicht typkorrekt sein.


B) Validate-Annotationen (Compile-time Checks)

Ziel: harte Garantien (z.B. Plattform, ABI, Layout-Annahmen), ohne allgemeines CTFE.

Kernfeature: @assert(predicate, msg?)

Zwei sinnvolle „Stufen“:

  1. Early assert (nur Build/Target/Feature):
  2. Predicate-DSL wie bei @cfg
  3. läuft direkt nach cfg_prune
  4. Beispiele: „Linux only“, „Feature X required“

  5. Late / type-aware assert (Layout/Size/Align):

  6. läuft nach Typauflösung/Layoutberechnung
  7. erlaubt zusätzlich Compiler-Queries wie sizeof(T), alignof(T), layoutof(T)
  8. geeignet für FFI ABI Checks

Das kann entweder über zwei Namen erfolgen: - @assert(...) (early) - @assert_type(...) (late)

oder über explizite Stages. Zwei Namen sind in der Praxis klarer.


C) Diagnose-Annotationen (Warnings/Lints)

Ziel: Entwicklerführung, ohne Semantik zu verändern.

Kernfeature: @deprecated(...)

Semantik: - Warnung bei Verwendung, nicht beim bloßen Import. - Payload optional: message, since, replacement (alles rein diagnostisch).

Optional ergänzend: - @lint.allow(...), @lint.deny(...) (wenn Lints/Rules existieren) - @inline, @noinline als reine Codegen-Hints (falls nötig)


D) Tool-/Build-Registrierung (deterministisch, statisch)

Ziel: Compiler sammelt strukturierte Informationen und stellt sie dem Tooling zur Verfügung.

Kernfeature: @test - Markiert Testfunktionen - Compiler validiert ggf. Signatur (z.B. () -> void oder () -> Result) - Compiler/Tool erzeugt Test-Manifest/Registry - Ausführung erfolgt in einem separaten Test-Modus, nicht im normalen Build

Optionen: - @test.ignore("reason") - @test.only (nur selektiv)


E) Runtime-Hooks / „Lang Items“ (Jade-verdratet)

Ziel: explizit deklarieren, welches Symbol eine spezielle Rolle im Runtime-Vertrag hat.

Beispiele: - globaler Error-/Panic-Handler - Allocator/Arena-Provider (Default Storage) - Entry Point (main) - Modul-Init/Deinit Hooks (wenn nötig) - GC root enumerator (falls relevant) - Scheduler Hook (falls relevant)

Konzeptionell: - @lang_item("global_error_handler") def handle(err: Error) -> Never { ... }

Regeln: - pro Rolle genau eine Definition (pro Link-Unit/Programm, definieren!) - Signatur ist strikt vorgegeben - Fehlen: Default aus stdlib oder Compile-Error (klar definieren) - Doppelt: Compile-Error


5. Predicate-DSL (für @cfg und frühe @assert)

Ziel: Minimal, deterministisch, keine User-Funktionen, kein CTFE.

Erlaubt: - Literale: bool, int, string - Operatoren: &&, ||, !, ==, != (optional <, >) - Zugriff auf Build-Kontext: - target.* (os, arch, ptr_width, endian, …) - build.* (profile, sanitize, …) - Funktion: feature("name")

Wichtig: Unbekannte Keys/Features sind Compile-Errors (Tippfehler sind zu teuer).


6. @intrinsic vs. @compiler

6.1 @intrinsic als Lowering-Markierung

@intrinsic ist eine Compiler-Annotation auf einer Funktionsdeklaration. Sie signalisiert dem Compiler, dass diese Funktion nicht als normaler JDL-Aufruf emittiert wird, sondern auf eine rohe VM- oder Compiler-Spezialbehandlung gesenkt wird (Lowering).

Normativ: - @intrinsic ist eine Annotation, kein Namespace. - Eine mit @intrinsic markierte Deklaration beschreibt eine stabile Compiler-/VM-Grenze. - Aufrufe auf solche Deklarationen werden beim Lowering auf eine spezielle IR-/VM-Repräsentation abgebildet und nicht wie gewöhnliche JDL-Funktionsaufrufe emittiert. - @intrinsic hat nichts mit @compiler::... als compiler-provided Namespace zu tun.

Beispielhaft (Ziel-Namespace: jade::vm, Syntax konzeptionell):

// spekulativ — Deklaration einer intrinsischen Spawn-Funktion im Modul jade::vm
@intrinsic
pub def spawn(fn: Handle, args: Handle, mailbox: MailboxConfig) -> Handle

6.2 Rolle von @compiler in diesem Bild

@compiler ist ein compiler-provided Namespace.
Er ist nicht „das gleiche“ wie Compiler-Annotationen, kann aber ihre Predicate-/Query-Schicht liefern.

Saubere Trennung:

  • Compiler-Annotationen (Syntax/AST-Knoten, wirken in Compiler-Phasen):
  • @cfg, @assert, @deprecated, @test, @lang_item, …

  • Intrinsics (Queries/Primitives, die die Sprache nicht anders ausdrücken kann):

  • Build/Target/Feature Fakten: @compiler::target::*, @compiler::build::*, @compiler::feature("x")
  • Layout/Type Queries: @compiler::sizeof(T), @compiler::alignof(T), @compiler::layoutof(T) (später)
  • Low-level Ops: trap, atomics, bitcast, etc. (separat und effect-gebunden)

Empfehlung: Predicates dürfen intern auf @compiler basieren, aber @cfg selbst bleibt eine Annotation, damit echtes AST-Pruning möglich ist.


7. MVP-Whitelist für @... (empfohlen)

Minimal, aber brauchbar:

  1. @cfg(predicate)
  2. @assert(predicate, msg?) (early)
  3. @assert_type(predicate, msg?) (late, optional wenn Layout-Queries verfügbar)
  4. @deprecated(msg?, since?, replacement?)
  5. @test, @test.ignore(reason), @test.only
  6. @lang_item(name) (Runtime-Hooks / special symbols)
  7. @intrinsic (Lowering-Markierung für rohe VM-/Compiler-Grenzen)
  8. optional: @lint.allow/deny, @inline/@noinline (nur wenn wirklich nötig)

Alles andere: Typ-Meta (TypeFns, Refinements, Generatoren, Collector).


8. Offene Punkte (gezielt, damit es nicht ausufert)

  1. Init/Load Policy für Registries
  2. Link-time fix? Load-time init? lazy?
  3. Was ist garantiert wann verfügbar?

  4. Scope/Link-Unit Definition für @lang_item

  5. pro Modul? pro Package? pro Programm?
  6. Override-Regeln (stdlib default vs user override)

  7. Welche target.* und build.* Keys sind garantiert?

  8. minimaler Satz definieren, rest optional

  9. Wie streng sind Unknown-Annotations?

  10. Empfehlung: unbekannte @... sind Compile-Error, um Wildwuchs zu verhindern
  11. Ausnahme: wenn du dokumentarische @doc willst, dann explizit als Typ-Meta statt @

  12. @mock ist nicht Teil der MVP-Whitelist.

  13. Vor einer Aufnahme muss entschieden werden, ob @mock rein compile-time selection bleibt oder ob es dynamisches Verhalten impliziert.
  14. Nur die compile-time Variante wäre mit Determinismus vereinbar (@cfg(build.test), Test-Build-gebundene Resolver-Auswahl, kein Monkey-Patching).
  15. Solange diese Entscheidung nicht normativ gefallen ist, bleibt @mock ein offener Punkt und keine freigegebene Compiler-Annotation.

9. Zusammenfassung

  • @ steht in JDL für Compiler-Annotations: deterministisch, minimal, Jade-verdrahtet.
  • @cfg liefert echtes AST-Pruning vor Typcheck.
  • @assert/@assert_type liefern compile-time Garantien ohne CTFE.
  • @deprecated und @test sind sichere, deterministische Diagnostik/Registrierung.
  • Runtime-Hooks (Lang Items) sind deklarative Bindungen von Symbolen an Jade-Verträge.

Ende (Working Draft)