Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Declaration Specs and Language Lowering

This chapter defines the ownership model for structured declarations. It is the accepted design direction for spec/* and lang/*; pre-0.6.8 compatibility paths that do not yet follow it are described in 0.6.8 Legacy Compatibility and Migration.

Decision

Declaration specs describe what to declare. A language adapter decides whether that intent is representable and owns how to spell it. Specs do not assemble a target-language grammar by interpreting a shared collection of keywords, separators, placement enums, or ordering flags.

The complete pipeline is:

builder
  |
  v
declaration spec                 target-independent intent
  |
  +-- intrinsic validation      invariants of the intent itself
  |
  +-- capability validation     target-specific representability
  |
  v
language-local lowering         exact grammar, spelling, and token order
  |
  v
CodeBlock / CodeNode::TypeRef    structured target-language rendering IR
  |
  +-- import and alias resolution
  +-- layout and indentation
  |
  v
source text

This is a compiler pipeline, not a general declaration-formatting engine.

Ownership

ConcernOwnerExamples
Declaration intentspec/*Name, parameters, result type, type parameters, bounds, members, visibility intent, modifiers, body
Intrinsic coherencespec/*Non-empty names, internally consistent parameter lists, valid builder state
Target representabilitylanguage capabilities and validationWhether a context supports type parameters, requires typed parameters, permits a body, or accepts a constructor
Target grammarlanguage adapterKeywords, ordering, placement, punctuation, modifier spelling, constructor syntax
Structured outputCodeBlockTarget literals plus semantic TypeRef, nesting, statement, and layout nodes
Final text mechanicsrendererImports, aliases, indentation, width decisions, and string emission

The ownership test is deliberately simple:

  • A fact about the requested declaration belongs to the spec.
  • A statement that the target supports, requires, or forbids a semantic fact belongs to capability validation.
  • A decision about which token appears, where it appears, or in what order it appears belongs to language-local lowering.
  • A decision about import names, indentation, width, or document layout belongs to the renderer.

Declaration Specs

A declaration spec is a target-independent declaration model, not the syntax tree of a hypothetical universal language. It may be richer than any one target. A target adapter either lowers the requested semantics or returns a validation error; it must not silently discard unsupported intent.

For example, one function declaration may contain:

name: id
type parameters: T
parameters: x of type T
result: T
body: ...

That intent can become:

Kotlin: fun <T> id(x: T): T
Rust:   fn id<T>(x: T) -> T
Java:   <T> T id(T x)

There is no semantic type parameter placement property in the declaration. Placement exists only after selecting a target grammar.

Specs can contain target-specific CodeBlock payloads for bodies, raw annotations, suffixes, or other escape hatches. These payloads are explicitly opaque to generic specs and shared lowerers: their presence does not make the declaration shell or its grammar a shared syntax model. Lowering composes them structurally and preserves their TypeRef nodes, but does not reinterpret their literal syntax. A private Python validator recognizes the documented 0.6.8 is_static plus decorator pattern as a frozen adapter-local compatibility exception; new behavior must use semantic intent instead of extending it.

Capabilities Are Semantic

The shared capability vocabulary describes representability. For example, ParametricPolymorphism says that a declaration context can express type parameters; TypedParameters says that parameter types are supported or required; and FunctionBodyPolicy says whether a body is legal. None of these concepts describes the position or spelling of a token.

Capabilities may be contextual. A target can support a bodyful top-level function while forbidding a body on an interface member, or support ordinary methods while rejecting constructors. Such differences remain semantic validation rules even though the rules are language-specific.

If a proposed capability cannot be defined without mentioning a keyword, delimiter, token order, or formatting example, it is probably target grammar rather than a semantic capability.

Language-Local Lowering

The external declaration seams first validate classified intent and then lower a complete validated declaration into a structured block:

#![allow(unused)]
fn main() {
extern crate sigil_stitch;
use sigil_stitch::code_block::CodeBlock;
use sigil_stitch::error::SigilStitchError;
use sigil_stitch::lang::{FunctionIntent, ValidatedFunction};
trait Example {
fn validate_function(
    &self,
    function: FunctionIntent<'_>,
) -> Result<(), SigilStitchError>;
fn lower_function(
    &self,
    function: ValidatedFunction<'_>,
) -> Result<CodeBlock, SigilStitchError>;
}
}

Enum variants use the same shape at sequence granularity:

#![allow(unused)]
fn main() {
extern crate sigil_stitch;
use sigil_stitch::code_block::CodeBlock;
use sigil_stitch::error::SigilStitchError;
use sigil_stitch::lang::{ValidatedVariants, VariantIntent};
trait Example {
fn validate_variants(&self, variants: VariantIntent<'_>)
    -> Result<(), SigilStitchError>;
fn collect_variant_validation_errors(
    &self,
    variants: VariantIntent<'_>,
    errors: &mut Vec<SigilStitchError>,
);
fn lower_variants(&self, variants: ValidatedVariants<'_>)
    -> Result<CodeBlock, SigilStitchError>;
}
}

Fields also cross the adapter boundary as one complete sequence:

#![allow(unused)]
fn main() {
extern crate sigil_stitch;
use sigil_stitch::code_block::CodeBlock;
use sigil_stitch::error::SigilStitchError;
use sigil_stitch::lang::{FieldSequenceIntent, ValidatedFields};
trait Example {
fn validate_fields(&self, fields: FieldSequenceIntent<'_>)
    -> Result<(), SigilStitchError>;
fn collect_field_validation_errors(
    &self,
    fields: FieldSequenceIntent<'_>,
    errors: &mut Vec<SigilStitchError>,
);
fn lower_fields(&self, fields: ValidatedFields<'_>)
    -> Result<CodeBlock, SigilStitchError>;
}
}

Computed properties cross as one complete semantic declaration:

#![allow(unused)]
fn main() {
extern crate sigil_stitch;
use sigil_stitch::code_block::CodeBlock;
use sigil_stitch::error::SigilStitchError;
use sigil_stitch::lang::{PropertyIntent, ValidatedProperty};
trait Example {
fn validate_property(
    &self,
    property: PropertyIntent<'_>,
) -> Result<(), SigilStitchError>;
fn collect_property_validation_errors(
    &self,
    property: PropertyIntent<'_>,
    errors: &mut Vec<SigilStitchError>,
);
fn lower_property(
    &self,
    property: ValidatedProperty<'_>,
) -> Result<Vec<CodeBlock>, SigilStitchError>;
}
}

Relationships among different member families use a validation-only owner view rather than another lowering abstraction:

#![allow(unused)]
fn main() {
extern crate sigil_stitch;
use sigil_stitch::error::SigilStitchError;
use sigil_stitch::lang::TypeMembersIntent;
trait Example {
fn validate_type_members(
    &self,
    members: TypeMembersIntent<'_>,
) -> Result<(), SigilStitchError>;
fn collect_type_members_validation_errors(
    &self,
    members: TypeMembersIntent<'_>,
    errors: &mut Vec<SigilStitchError>,
);
}
}

FunctionIntent provides read-only access after context and form classification and crate-owned semantic validation against the selected adapter. ValidatedFunction can only be constructed by the crate after the adapter’s additional validation succeeds. FunSpec::emit() remains the convenience facade: it delegates validation and lowering without interpreting target grammar switches itself.

VariantIntent provides the owner name and kind, every variant in declaration order, whether ordinary members follow, structured-constructor arity evidence, and whether opaque members may provide target-specific constructor syntax. The adapter derives first/last position and owns preambles, payload grammar, separators, and section termination for the complete sequence. Variant capabilities name semantic forms—discriminant, constructor arguments, positional payload, record payload, and attributes—not their spelling. The additive collector reports independent sibling failures; ValidatedVariants is constructed only after intrinsic, profile, and every adapter-local validation phase succeeds.

FieldSequenceIntent provides every field in declaration order and a semantic FieldContext: direct emission, ordinary type members, or a variant record payload. Owner and variant names are included when they exist. Field profiles declare supported and required semantic capabilities for each context, while the adapter-local validator handles identifier rules, escaped-name collisions, modifier combinations, and target-specific restrictions. The additive collector retains independent sibling failures during FileSpec validation; ValidatedFields is created only after intrinsic, profile, and adapter-local validation all succeed. lower_fields() owns the sequence’s complete grammar, including documentation, annotations, access sections, tags, separators, and declarator restrictions.

Optional presence and optional values are separate semantics. A field marked with FieldSpec::is_optional() may be absent from its containing value and requests FieldCapability::OptionalPresence. A TypeName::Optional(T) field is still present but may hold the target language’s option or null representation. An adapter must not substitute one meaning for the other.

PropertyIntent provides one property and its semantic PropertyContext: direct emission with the legacy declaration context, or a member of an owning TypeKind. Property profiles distinguish explicit type information, read and write behavior, attributes, and static behavior. A getter or setter body is semantic implementation input; whether the target expresses it as accessor declarations, a field-style computed property, or ordinary target-local methods belongs entirely to lower_property(). ValidatedProperty is constructed only after intrinsic, profile, and adapter-local validation succeeds.

TypeMembersIntent provides the owning type’s name and kind together with its semantic fields, computed properties, and explicit methods. It is constructed once after the per-family validation passes. The crate rejects exact duplicate property names; the adapter owns collisions created by target lowering, such as PHP’s case-insensitive generated accessor names colliding with another property accessor or an explicit method. The intent contains no target grammar, has no validated wrapper, and has no lowering method: every accepted property still follows ValidatedProperty -> lower_property() independently.

Each adapter owns the complete ordering and spelling of a declaration. Private leaf helpers may render structured fragments such as a parameter list or body, but do not choose their relative order. Related adapters may additionally share a genuinely family-specific lowering helper. An adapter can bypass either without adding a new variant to a shared grammar interface.

A useful locality test is to add a language with a previously unseen syntax. The change should be confined to that adapter, its private helpers, and its tests. If the change requires a new shared placement enum and new branches in a generic spec emitter, target grammar has crossed the seam.

Compatibility and Migration

Public declaration grammar that was already part of the 0.6.8 adapter surface may remain behind a deprecated, frozen compatibility lowerer. Compatibility is not permission to extend that design:

  • Do not add new shared declaration-placement enums, flags, or keyword fields.
  • Do not add new branches in specs to interpret target grammar.
  • New built-in behavior should enter through a complete language-owned lowering seam.
  • Concepts introduced after 0.6.8 may be changed or removed instead of being preserved as another compatibility layer.
  • Existing built-in adapters should migrate incrementally, with rendered-output tests at the adapter seam and parity coverage across direct and pretty paths.

Compatibility is bounded by validity: a built-in adapter may restrict an old entry point rather than emit malformed or unverifiable target code. The full version boundary, deprecated-surface matrix, builder recipes, external-adapter sequence, and remaining type-lowering transition are centralized in 0.6.8 Legacy Compatibility and Migration.

Scope of This Decision

This decision governs declaration grammar interpreted by spec/*. It does not prohibit shared semantic data, structured rendering nodes, or private reusable helpers. It also does not by itself redesign lower-level seams such as TypeName presentation or block layout; those mechanisms have their own design documents and must be evaluated against their own callers and invariants.