Adding a Language
sigil-stitch supports new languages by implementing two traits: RendererLang (renderer-only methods) and CodeLang (spec-layer methods). CodeLang extends RendererLang, so implementing CodeLang requires both. If you only need CodeBlock-level rendering without specs, RendererLang alone is sufficient.
RendererLang covers rendering essentials. CodeLang adds declaration
validation, materialization, and file-level behavior. The current trait also
contains pre-0.6.8 syntax configuration and structured emission hooks with
compatibility defaults.
Do not treat those declaration syntax structs as an extensible universal grammar. New syntax dimensions belong in complete language-local lowering. See Declaration Specs and Language Lowering for the ownership model and 0.6.8 Legacy Compatibility and Migration for the deprecated surface.
This guide walks through the process using a hypothetical language, with references to real implementations you can study.
Overview
Adding a language takes five steps:
- Create
src/lang/your_lang.rsimplementingCodeLang - Add
pub mod your_lang;tosrc/lang/mod.rs - Write integration tests in
tests/ - Run
just blessto generate golden files - Bridge the remaining transitional type-declaration path
If your language has tokenizer conflicts in sigil_quote! that the universal heuristics
can’t handle (e.g., shell flags, Go channel operators), you may also need to add a
MacroLang variant. See Language-Aware Tokenizer for details.
The RendererLang Trait
These methods are used by the renderer (code_renderer.rs) and type rendering:
Required Methods
Only two methods have no default:
| Method | Example (TypeScript) | Purpose |
|---|---|---|
file_extension() | "ts" | File extension for output files |
line_comment_prefix() | "//" | Single-line comment prefix |
Common Overrides
| Method | Default | Purpose |
|---|---|---|
reserved_words() | Empty | Words that need escaping |
render_string_literal() | C-style double quotes | Language-specific string quoting |
render_verbatim_string() | Delegates to render_string_literal() | Minimal escaping for interpolated strings |
block_syntax() | Brace-delimited blocks | Delimiters, indentation, and terminators |
block_open_for_intent() | Delegates to legacy block_open_for() | Map a BlockIntent role to an opener |
block_close_for_intent() | Delegates to legacy block_close_for() | Map a BlockIntent role to a closer |
type_presentation() | TypeScript-like forms | Compound type rendering |
generic_syntax() | Angle brackets | Generic application and constraints |
Override render_verbatim_string() if your language has string interpolation (e.g., Bash "$x", TypeScript `${x}`, Python f"{x}").
For keyword-delimited languages, implement block_open_for_intent() and
block_close_for_intent() as a local match over BlockIntent. The legacy
string-based block_open_for() / block_close_for() methods remain supported
only for old serialized nodes and external adapters.
rewrite_nodes() is available for renderer corrections that require a
tree-level view after macro expansion. Prefer intent-keyed structural rewrites
for blocks. Declaration grammar belongs to language-local lowering; the
existing declaration syntax structs are a compatibility path, not the place to
add another ordering or placement concept.
The CodeLang Trait
Extends RendererLang with the additional methods needed by the spec layer.
Implement capabilities() for new adapters. Return a local
LanguageCapabilities::strict() matrix, add TypeCapabilityProfiles with
with_types(), add FunctionCapabilityProfiles with with_functions(), and
add FieldCapabilityProfiles with with_fields(),
PropertyCapabilityProfiles with with_properties(), and
VariantCapabilityProfiles with with_variants() for every supported
declaration context or owning type kind.
Function profiles are keyed by both context (TopLevel, ReceiverMethod,
Member, or InterfaceMember) and form (Function, Constructor, or
Destructor). Omit a profile when that combination is unsupported. Include
ExplicitReturnType and TypedParameters only where the form can represent
them. Use with_required_capabilities() for semantic facts that every
declaration must provide, with_body_policy() for required or forbidden
implementation bodies, and with_incompatible_capabilities() for supported
features that cannot be combined. Use with_maximum_parameters() for
form-specific arity limits, such as a zero-parameter destructor. Adapters
written for sigil-stitch 0.6.8 inherit
LanguageCapabilities::permissive() so their existing CodeLang
implementations remain source-compatible.
Variant profiles distinguish Discriminant, ConstructorArguments,
PositionalPayload, RecordPayload, and Attributes. They must not encode
keywords, delimiters, placement, or separator policy. Omit the owner profile if
the language cannot represent variants for that TypeKind; use an empty
capability list when simple variants are valid but no richer form is.
Field profiles are keyed by FieldContext: direct member emission, ordinary
members of one TypeKind, or record payloads of one variant owner kind. They
distinguish explicit type information, initializers, attributes, static and
readonly fields, and OptionalPresence. Add ExplicitType to the required set
only where an untyped field cannot be valid. Optional presence means the member
may be absent; value nullability is expressed separately with
TypeName::Optional.
Property profiles are keyed by PropertyContext: direct member emission or a
member of one owning TypeKind. They distinguish explicit type information,
read access, write access, attributes, and static behavior. Require
ReadAccessor where a write-only computed property is invalid and require
ExplicitType where inference cannot preserve the declaration. Getter/setter
spelling and whether the target uses accessor declarations, a field-style
body, or ordinary methods are lowering decisions, not capabilities.
Declaration Lowering and Compatibility Methods
CodeLang::validate_function() receives classified, read-only FunctionIntent
after sigil-stitch applies its semantic capability matrix against the actual
adapter. An override returns Result<(), SigilStitchError> and can add
target-local checks, but cannot construct or bypass ValidatedFunction.
CodeLang::lower_function() receives the validated view and returns a
structured CodeBlock. New adapters implement this method as the owner of the
target’s complete function grammar. Both views expose the function form and
context as well as names, types, parameters, modifiers, annotations,
constraints, delegation, suffix escape hatches, and the body.
CodeLang::validate_variants() and CodeLang::lower_variants() are the
corresponding complete-sequence seams for enum variants. Adapters that can find
multiple independent target-local errors override
collect_variant_validation_errors() as the additive validation entry point;
its default appends the single validate_variants() result. VariantIntent
exposes the owner, ordered variants, payloads, annotations, following-member
state, structured-constructor arity evidence, and the presence of opaque members. The
lowerer derives position and owns all target grammar. Use
AnnotationSpec::emit_with_syntax() when a local annotation spelling must keep
an importable annotation name as a structured %T reference.
CodeLang::validate_fields() and CodeLang::lower_fields() form the
corresponding complete-sequence seam for fields. FieldSequenceIntent exposes
the semantic context, owner names when present, and the ordered read-only field
data. Override collect_field_validation_errors() as well when independent
sibling failures should survive file-level aggregation; its default appends
the single validate_fields() result. ValidatedFields is crate-constructed
after intrinsic, profile, and adapter-local validation. The lowerer owns all
field grammar, including documentation and annotation order, access sections,
tags, delimiters, and terminators. Keep every field type in a %T slot and
compose initializers or raw annotations as nested CodeBlocks.
CodeLang::validate_property() and CodeLang::lower_property() form the
corresponding seam for one computed property. PropertyIntent exposes the
semantic context, owner when present, property type, read and write bodies,
modifiers, documentation, and attributes. Override
collect_property_validation_errors() when multiple independent target-local
failures should survive file-level aggregation. ValidatedProperty is
crate-constructed after intrinsic, profile, and adapter-local validation. The
lowerer returns Vec<CodeBlock> because one property may become separate read
and write accessor declarations. Preserve its type in %T slots and compose
bodies and raw annotations structurally.
CodeLang::validate_type_members() is the validation-only seam for
relationships among one type’s fields, computed properties, and explicit
methods after their per-family validation has run. Override
collect_type_members_validation_errors() when several independent owner-wide
failures should be aggregated. Use it for target-derived relationships such as
case-folded accessor/method collisions. It has no matching lowerer: properties
still lower one at a time through lower_property(), and the intent must not
grow placement, namespace-layout, or other grammar policy.
The remaining interface mixes semantic validation hooks with older grammar fragments used by compatibility lowerers. Grammar-oriented methods must be absorbed by complete language-local lowering rather than multiplied:
| Method | Example | Purpose |
|---|---|---|
capabilities() | Strict type, function, field, property, and variant profiles | Declare semantic representability by context and form |
render_visibility() | "public ", "pub " | Visibility prefix |
function_keyword() | "function", "fn" | Function declaration keyword |
abstract_modifier_capability() | AbstractMethod, VirtualMethod | Semantic meaning of the legacy abstract modifier |
function_form() | Function, Constructor, Destructor | Classify declaration form for capability validation |
constructor_name_matches() | constructor, init, or declaring type | Recognize implicit constructor spellings with or without an owning type |
static_constructor_name_matches() | true / false for name and owner | Decide whether a constructor-shaped static member is still a constructor |
constructor_name_with_return_type_is_function() | true / false | Let an explicit return type disambiguate an owner-named ordinary method |
constructor_name_is_valid() | true / false for name and owner | Reject explicitly marked constructors whose names violate local syntax |
type_member_declaration_context() | Member, InterfaceMember | Select concrete or contract member rules for each TypeKind |
abstract_type_modifier_is_valid() | true / false for one TypeKind | Restrict explicit abstract type declarations to valid kinds |
function_parameters_are_typed() | true / false for the complete list | Refine required typing for receiver spellings or shared annotations |
function_body_policy() | Required, Forbidden, Optional | Refine profile body policy when modifiers change the rule |
maximum_function_parameters() | maximum arity or None | Refine profile arity when modifiers change the limit |
function_visibility_is_valid() | true / false | Reject form- or modifier-specific visibility before emission |
function_parameters_require_trailing_defaults() | true / false | Require every defaulted parameter to follow required parameters |
validate_function_type_constraints() | Result<(), SigilStitchError> | Validate whether the complete type-constraint set is semantically representable |
requires_complete_function_type_information() | true / false | Require partial type metadata to form one complete typed declaration |
constructor_return_type_is_valid() | true / false for one type | Restrict constructor return annotations after capability validation |
validate_function() | FunctionIntent -> Result<(), _> | Add target-local checks after crate-owned semantic validation |
lower_function() | ValidatedFunction -> CodeBlock | Own complete function grammar; defaults to the frozen compatibility lowerer |
validate_fields() | FieldSequenceIntent -> Result<(), _> | Add target-local checks after crate-owned field validation |
collect_field_validation_errors() | FieldSequenceIntent + error sink | Add independent target-local sibling errors during file validation |
lower_fields() | ValidatedFields -> CodeBlock | Own complete field-sequence grammar; defaults to frozen compatibility lowering |
validate_property() | PropertyIntent -> Result<(), _> | Add target-local checks after crate-owned property validation |
collect_property_validation_errors() | PropertyIntent + error sink | Add independent target-local property errors during file validation |
lower_property() | ValidatedProperty -> Vec<CodeBlock> | Own complete property grammar; defaults to frozen compatibility lowering |
validate_type_members() | TypeMembersIntent -> Result<(), _> | Add target-local checks across semantic member families after per-family validation |
collect_type_members_validation_errors() | TypeMembersIntent + error sink | Add independent target-derived cross-member errors during file validation |
validate_variants() | VariantIntent -> Result<(), _> | Add target-local checks after crate-owned sequence validation |
collect_variant_validation_errors() | VariantIntent + error sink | Add independent target-local sibling errors during file validation |
lower_variants() | ValidatedVariants -> CodeBlock | Own complete variant-sequence grammar; defaults to frozen compatibility lowering |
type_keyword() | "class", "struct" | Type declaration keyword |
methods_inside_type_body() | true / false | Legacy structural switch used by the compatibility type emitter |
methods_inside_type_body() is a legacy switch used by the transitional
TypeSpec emitter. Existing adapters may keep an override until complete type
lowering owns whether methods appear in the declaration body or a separate
block. New declaration families must not reuse it as a general placement hook.
See the legacy surface matrix.
Renderer Configuration
block_syntax(), generic_syntax(), and type_presentation() are lower-level
renderer and type-presentation seams. The deprecated declaration configuration
structs have a different role and are documented centrally in 0.6.8 Legacy
Compatibility and Migration.
Do not add public flags, enums, or fields to them for a new language.
block_syntax()
Returns BlockSyntaxConfig controlling block delimiters and formatting:
| Field | Default | Purpose |
|---|---|---|
block_open | " {" | Opening delimiter. Python overrides to ":". |
block_close | "}" | Closing delimiter. Python overrides to "" (indent-only). |
indent_unit | " " (2 spaces) | Indentation per level. |
uses_semicolons | true | Statement terminator behavior. |
field_terminator | "," | After each field. Java/C++ override to ";". |
type_close_terminator | (default) | Terminator after closing brace for types. |
bases_close | (default) | Closing syntax for base-class lists. |
generic_syntax()
Returns GenericSyntaxConfig controlling generic/type-parameter syntax:
| Field | Default | Purpose |
|---|---|---|
open | "<" | Generic opening bracket. Go overrides to "[". |
close | ">" | Generic closing bracket. Go overrides to "]". |
application_style | (default) | How generics are applied to types. |
constraint_keyword | ": " | Generic bounds keyword. Java/TS override to " extends ". |
constraint_separator | " + " | Between multiple bounds. Java/TS override to " & ". |
context_bound_keyword | (default) | Context bound syntax (e.g. Scala’s :). |
type_presentation()
Returns TypePresentationConfig controlling how semantic types (arrays, optionals, maps, tuples, references, function types, etc.) are rendered. See the Type Presentation section below for details.
Standalone Override Methods
These methods don’t belong to a config struct but have sensible defaults you can override:
escape_reserved()– how reserved words are escaped.qualify_import_name()– receives the module, original name, and resolved name. The default returns the resolved name; Go prefixes its package and Haskell uses a module-qualified original name when an alias was assigned, paired with aqualifiedimport for that symbol.module_separator()– returnsOption<&str>. DefaultNone. Override toSome("::")(Rust/C++) orSome(".")(Go/Python/Java/etc.) to enableTypeName::qualified()inline rendering.type_kind_suffix()– suffix after type close for specific type kinds.emit_newtype_decl()– emits a structuredCodeBlockfor a newtype. The default is the Rust tuple structstruct Name(Inner);.fun_block_open()– custom block opener for functions.type_header_block_open()– custom block opener for type headers.emit_type_context()– optional structured context for split function signatures.type_body_prefix()– content emitted before the type body.type_body_suffix()– content emitted after the type body.emit_type_close_suffix()– optional structured suffix after a type’s close delimiter, such as Haskellderiving.render_type_param_kind()– how type parameters are annotated with variance.line_comment_suffix()– suffix for line comments (default"").
Deprecated standalone declaration hooks such as preamble ordering, optional- field style, and property style are listed with their replacements in the legacy surface matrix.
render_imports() receives a deduplicated, alias-resolved ImportGroup and
emits the file’s import header. render_doc_comment() emits spec-level doc
comments. Study src/lang/typescript.rs for ES module imports or
src/lang/rust.rs for use paths.
The three emit_* type hooks return Result so construction failures reach
FileSpec::render(). emit_type_context() and
emit_type_close_suffix() return Ok(None) when the language has no fragment
to add. Use Arg::TypeName or %T for every semantic type and compose child
blocks structurally; do not render a TypeName to a string inside a hook.
These fragment hooks return blocks without a trailing newline. A complete
sequence lowerer such as lower_fields() instead owns every line boundary its
sequence requires, including the boundary after its final declaration; the
caller owns only spacing between declaration families.
Step-by-Step Walkthrough
1. Create the language file
Create src/lang/your_lang.rs. Keep semantic types in %T slots. Fragment
hooks omit surrounding whitespace, while complete lowerers own the internal
and terminating line boundaries required by their grammar. Hook errors should
be returned unchanged.
use sigil_stitch::code_block::{Arg, CodeBlock};
use sigil_stitch::error::SigilStitchError;
use sigil_stitch::import::ImportGroup;
use sigil_stitch::lang::capability::{
FieldCapability, FieldCapabilityProfile, FieldContext, LanguageCapabilities,
};
use sigil_stitch::lang::config::{
BlockSyntaxConfig, GenericSyntaxConfig, TypeDeclSyntaxConfig,
};
use sigil_stitch::lang::{CodeLang, RendererLang, ValidatedFields, ValidatedFunction};
use sigil_stitch::spec::modifiers::{DeclarationContext, TypeKind, Visibility};
use sigil_stitch::spec::where_spec::{TypeParamSpec, render_type_params};
use sigil_stitch::type_name::TypeName;
#[derive(Debug, Clone, Default)]
pub struct YourLang;
impl YourLang {
pub fn new() -> Self {
Self
}
}
const RESERVED: &[&str] = &["if", "else", "for", "while", /* ... */];
const FIELD_CAPABILITIES: &[FieldCapability] = &[
FieldCapability::ExplicitType,
FieldCapability::Initializer,
];
const REQUIRED_FIELD_CAPABILITIES: &[FieldCapability] =
&[FieldCapability::ExplicitType];
const FIELD_PROFILES: &[FieldCapabilityProfile<'_>] = &[
FieldCapabilityProfile::new(
FieldContext::Direct(DeclarationContext::Member),
FIELD_CAPABILITIES,
)
.with_required_capabilities(REQUIRED_FIELD_CAPABILITIES),
FieldCapabilityProfile::new(
FieldContext::TypeMember(TypeKind::Class),
FIELD_CAPABILITIES,
)
.with_required_capabilities(REQUIRED_FIELD_CAPABILITIES),
];
impl RendererLang for YourLang {
fn file_extension(&self) -> &str { "yl" }
fn reserved_words(&self) -> &[&str] { RESERVED }
fn line_comment_prefix(&self) -> &str { "//" }
fn render_string_literal(&self, s: &str) -> String {
format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\""))
}
fn block_syntax(&self) -> BlockSyntaxConfig<'_> {
BlockSyntaxConfig {
uses_semicolons: true,
indent_unit: " ",
field_terminator: ";",
..Default::default()
}
}
fn generic_syntax(&self) -> GenericSyntaxConfig<'_> {
GenericSyntaxConfig {
constraint_keyword: " extends ",
constraint_separator: " & ",
..Default::default()
}
}
}
impl CodeLang for YourLang {
fn capabilities(&self) -> LanguageCapabilities<'_> {
// Add the language's exact type, function, and variant profiles too.
LanguageCapabilities::strict().with_fields(FIELD_PROFILES)
}
fn render_doc_comment(&self, lines: &[&str]) -> String {
let mut out = String::from("/**\n");
for line in lines {
out.push_str(&format!(" * {line}\n"));
}
out.push_str(" */\n");
out
}
fn render_imports(&self, imports: &ImportGroup) -> String {
let mut out = String::new();
for entry in imports.entries() {
out.push_str(&format!(
"import {{ {} }} from \"{}\";\n",
entry.resolved_name(),
entry.module,
));
}
out
}
fn lower_function(
&self,
function: ValidatedFunction<'_>,
) -> Result<CodeBlock, SigilStitchError> {
let mut block = CodeBlock::builder();
block.add(
"%Lfunction %L(",
(
self.render_visibility(
function.modifiers().visibility,
function.declaration_context(),
),
function.name(),
),
);
for (index, parameter) in function.parameters().iter().enumerate() {
if index > 0 {
block.add(",%W", ());
}
block.add("%L: %T", (parameter.name(), parameter.param_type().clone()));
}
block.add(")", ());
if let Some(return_type) = function.return_type() {
block.add(": %T", return_type.clone());
}
if let Some(body) = function.body() {
block.add(" {", ());
block.add_line();
block.add("%>", ());
block.add_code(body.clone());
block.add_line();
block.add("%<}", ());
} else {
block.add(";", ());
}
block.build()
}
fn lower_fields(
&self,
fields: ValidatedFields<'_>,
) -> Result<CodeBlock, SigilStitchError> {
let mut block = CodeBlock::builder();
for field in fields.fields() {
if !field.doc().is_empty() {
let lines: Vec<&str> = field.doc().iter().map(String::as_str).collect();
block.add("%L", self.render_doc_comment(&lines));
block.add_line();
}
block.add(
"%L%L: %T",
(
self.render_visibility(
field.modifiers().visibility,
DeclarationContext::Member,
),
self.escape_field_name(field.name()),
field.field_type().clone(),
),
);
if let Some(initializer) = field.initializer() {
block.add(" = %L", initializer.clone());
}
block.add(";", ());
block.add_line();
}
block.build()
}
// Remaining spec support methods...
fn render_visibility(&self, vis: Visibility, _ctx: DeclarationContext) -> &str {
match vis {
Visibility::Public => "public ",
Visibility::Private => "private ",
Visibility::Protected => "protected ",
_ => "",
}
}
fn type_keyword(&self, kind: TypeKind) -> &str {
match kind {
TypeKind::Class => "class",
TypeKind::Interface | TypeKind::Trait => "interface",
TypeKind::Enum => "enum",
TypeKind::Struct => "class",
TypeKind::TypeAlias => "type",
TypeKind::Newtype => "class",
}
}
fn methods_inside_type_body(&self, _kind: TypeKind) -> bool { true }
fn emit_newtype_decl(
&self,
visibility: &str,
name: &str,
type_params: &[TypeParamSpec],
inner: &TypeName,
) -> Result<CodeBlock, SigilStitchError> {
let mut args = Vec::new();
let params = render_type_params(type_params, self, &mut args);
args.push(Arg::TypeName(inner.clone()));
CodeBlock::of(&format!("{visibility}opaque {name}{params} = %T"), args)
}
// Transitional type-declaration compatibility override. Do not add fields
// here for new grammar; move complete type lowering behind an adapter seam.
fn type_decl_syntax(&self) -> TypeDeclSyntaxConfig<'_> {
TypeDeclSyntaxConfig {
super_type_keyword: " extends ",
implements_keyword: " implements ",
..Default::default()
}
}
}
The runnable CodeLang rustdoc example compiles as part of cargo test --doc.
Use it as the contract reference when adding or changing structured hooks.
2. Register the module
Add to src/lang/mod.rs:
/// YourLang language support.
pub mod your_lang;
3. Write tests
Create a test directory tests/your_lang/ with a main.rs entry point and submodules:
tests/your_lang/main.rs:
mod golden;
mod quote_basic;
mod builder_basic;
tests/your_lang/quote_basic.rs – sigil_quote! macro tests:
use sigil_stitch::prelude::*;
fn render(block: &CodeBlock) -> String {
FileSpec::builder("test.yl")
.add_code(block.clone())
.build()
.unwrap()
.render(80)
.unwrap()
}
#[test]
fn test_basic_statement() {
let block = sigil_quote!(YourLang {
const x = 1;
});
golden::assert_golden("your_lang/basic_statement.yl", &render(&block));
}
tests/your_lang/builder_basic.rs – builder API tests (CodeBlock, TypeSpec, FunSpec, FileSpec).
4. Generate golden files
just bless
This runs all tests with BLESS=1, which creates test-goldens/your_lang/*.yl files from the actual output. Review them manually, then commit.
5. Bridge transitional type lowering
Run the full test suite and review golden file output. Implement function, field, property, and variant grammar in the corresponding complete lowering seams. Type declarations have not yet moved behind a complete language-local seam and may still require deprecated syntax accessors. Use existing fields only where they already express the target, and do not add a shared field or enum for an unseen grammar dimension. Follow the external-adapter migration sequence. Examples of remaining transitional overrides are:
- If types come before names (
int xinstead ofx: int): overridetype_decl_syntax()to settype_before_name,return_type_is_prefix - If generics use brackets instead of angle brackets: override
generic_syntax()to setopen,close
Reference Implementations
Study these existing implementations for patterns similar to your target:
| Language | File | Notable Patterns |
|---|---|---|
| TypeScript | src/lang/typescript.rs | ES module imports, type-only imports, single-quoted strings |
| Rust | src/lang/rust.rs | use paths, struct+impl split, pub(crate) visibility |
| Python | src/lang/python.rs | Indent-only blocks (no braces), docstrings inside body, from x import y |
| Go | src/lang/go.rs | Package-qualified names (http.Server), bracket generics, func keyword |
| C | src/lang/c.rs | Type-before-name, #include, __attribute__, struct close semicolon |
| C++ | src/lang/cpp.rs | virtual instead of abstract, #include + using, [[attributes]] |
| Bash | src/lang/bash.rs | Keyword-based block closers (fi/done/esac), source imports, shell escaping |
| Scala | src/lang/scala.rs | case class, trait, [T] generics, <: bounds, = {/} blocks |
| Haskell | src/lang/haskell.rs | Split signature style, where/indentation blocks, postfix generics, deriving |
| OCaml | src/lang/ocaml.rs | Postfix generics, let keyword, = /indentation blocks, open Module imports, module_block helper |
Type Presentation
When your language uses type expressions (generics, arrays, optionals, maps, etc.), you configure how each semantic type concept renders by returning a TypePresentationConfig from the type_presentation() accessor. You never build BoxDoc directly.
How it works
Each TypeName variant (Array, Optional, Map, etc.) uses your language’s TypePresentationConfig to determine the syntactic pattern via TypePresentation — a small enum:
GenericWrap { name }—name<P1, P2>using yourgeneric_syntax().open/generic_syntax().closePrefix { prefix }—prefix inner(e.g., Go[]T, Rust*const T)Postfix { suffix }—inner suffix(e.g., TypeScriptT[], KotlinT?)Surround { prefix, suffix }—prefix inner suffix(e.g., C++const T&, Cconst T*)Delimited { open, sep, close }—open P1 sep P2 close(e.g., Swift[K: V], Gomap[K]V)Infix { sep }—P1 sep P2(e.g., TypeScriptA | B, RustA + B)
Configuring type presentation
All fields in TypePresentationConfig have defaults matching TypeScript conventions. Override only when your language differs:
impl RendererLang for YourLang {
fn type_presentation(&self) -> TypePresentationConfig<'_> {
TypePresentationConfig {
// Array: default is Postfix { suffix: "[]" } (TS: T[])
// Override for Rust-style Vec<T>:
array: TypePresentation::GenericWrap { name: "Vec" },
// Optional: default is Infix { sep: " | " } with "null" literal
// Override for Kotlin-style T?:
optional: TypePresentation::Postfix { suffix: "?" },
// Map: default is GenericWrap { name: "Map" }
// Override for Go-style map[K]V:
map: TypePresentation::Delimited { open: "map[", sep: "]", close: "" },
// Tuple: default is Delimited { open: "(", sep: ", ", close: ")" }
// TS overrides to "[", "]" for [A, B] syntax. This shows Go-style (A, B):
tuple: TypePresentation::Delimited { open: "(", sep: ", ", close: ")" },
// Reference: default is Prefix { prefix: "" } (identity — for GC languages)
// Override for Rust-style &T:
reference: TypePresentation::Prefix { prefix: "&" },
// Function types: default is TypeScript (A, B) => R
function: FunctionPresentation {
keyword: "fn",
params_open: "(",
params_sep: ", ",
params_close: ")",
arrow: " -> ",
return_first: false,
curried: false,
wrapper_open: "",
wrapper_close: "",
},
..Default::default()
}
}
}
See Type Presentation for the full enum definition, all available fields, and examples for every supported language.