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

TypeName

This chapter describes the implemented 0.7 type-name-lowering contract.

TypeName is the type reference enum at the heart of sigil-stitch’s import tracking. When you use a TypeName with the %T format specifier in a CodeBlock, the library renders the type name in the output and records the import. At render time, FileSpec collects all recorded imports, deduplicates them, resolves naming conflicts, and emits the import header automatically.

TypeName carries semantic type structure and has no language generic parameter. At FileSpec::render() time, the selected RendererLang lowers one complete type name into structured target-language output or rejects it. Primitive, Qualified, and especially Raw values may still contain target-specific names or syntax.

Public type rendering is always language-aware. For normal generation, place a TypeName in a CodeBlock %T slot. FileSpec first applies the selected adapter’s source-tree rewrite, then lowers every type name, collects imports from the lowered blocks, resolves aliases, and renders the target syntax with no further rewrite or type lowering. Language-neutral rendering shortcuts are not exposed because they would flatten type references before representability checks and import resolution.

Import tracking

The two Importable constructors are the primary way to create types that generate import statements:

extern crate sigil_stitch;
use sigil_stitch::prelude::*;
use sigil_stitch::lang::typescript::TypeScript;
fn main() {
// Value import: import { User } from './models'
let user = TypeName::importable("./models", "User");

// Type-only import: import type { User } from './models'
let user = TypeName::importable_type("./models", "User");
}

When these types appear in a CodeBlock via %T, the import is tracked automatically. At file render time, all imports are collected, deduplicated, and emitted. Imports requesting the same local name form a peer conflict set. The default resolver uses encounter order only as a deterministic compatibility tie-break; a custom fallible resolver can assign a different complete set.

You can also set an explicit alias:

extern crate sigil_stitch;
use sigil_stitch::prelude::*;
fn main() {
let user = TypeName::importable("./other", "User")
    .with_alias("OtherUser");
// import { User as OtherUser } from './other'
// Rendered as: OtherUser
}

Primitives

Types that don’t need imports – built-in language types, type parameters, or any name that’s already in scope:

extern crate sigil_stitch;
use sigil_stitch::prelude::*;
fn main() {
let s = TypeName::primitive("string");
let n = TypeName::primitive("number");
let t = TypeName::primitive("T");  // type parameter
}

Qualified types

For types that should render with their full module path inline without generating an import statement:

extern crate sigil_stitch;
use sigil_stitch::prelude::*;
fn main() {
// Rust: serde_json::Value  (no `use serde_json::Value;`)
let val = TypeName::qualified("serde_json", "Value");

// Rust: super::Foo
let foo = TypeName::qualified("super", "Foo");

// Java: java.util.HashMap
let map = TypeName::qualified("java.util", "HashMap");
}

The selected language lowerer owns the separator between module and name: "::" for targets such as Rust and C++, and "." for targets such as Go, Python, Java, Kotlin, Scala, Swift, Dart, Haskell, and OCaml. A language that cannot preserve a qualified reference rejects it instead of silently dropping the module.

Qualified types work anywhere a TypeName is accepted, including inside generics:

extern crate sigil_stitch;
use sigil_stitch::prelude::*;
fn main() {
// Rust: std::collections::HashMap<String, serde_json::Value>
let map = TypeName::application(TypeName::qualified("std::collections", "HashMap"), vec![TypeArgument::Single(TypeName::primitive("String")), TypeArgument::Single(TypeName::qualified("serde_json", "Value"))]);
}

You can also convert an existing importable type to qualified rendering with .qualify():

extern crate sigil_stitch;
use sigil_stitch::prelude::*;
fn main() {
// Equivalent to TypeName::qualified("serde_json", "Value")
let val = TypeName::importable("serde_json", "Value").qualify();
}

Collections

Arrays

extern crate sigil_stitch;
use sigil_stitch::prelude::*;
fn main() {
// TypeScript: string[]
// Rust:       Vec<String>
// Go:         []string
let arr = TypeName::array(TypeName::primitive("string"));

// TypeScript: readonly number[]
let ro = TypeName::readonly_array(TypeName::primitive("number"));
}

Maps

extern crate sigil_stitch;
use sigil_stitch::prelude::*;
fn main() {
// Go:         map[string]User
// TypeScript: Record<string, User>
let m = TypeName::map(
    TypeName::primitive("string"),
    TypeName::importable("./models", "User"),
);
}

Tuples

extern crate sigil_stitch;
use sigil_stitch::prelude::*;
fn main() {
// Rust:   (String, i32)
// TS:     [string, number]
// Python: tuple[str, int]
// C++:    std::tuple<string, int>
let t = TypeName::tuple(vec![
    TypeName::primitive("string"),
    TypeName::primitive("number"),
]);

// Unit type (empty tuple): Rust ()
let unit = TypeName::unit();
}

Slices

extern crate sigil_stitch;
use sigil_stitch::prelude::*;
fn main() {
// Go: []User
let s = TypeName::slice(TypeName::primitive("User"));
}

Generics

For structured applications, use TypeName::application. A declaration’s bindings are separate GenericParamSpec values; a use refers to one by TypeName::parameter. Expansions preserve a complete pattern, not just a special final argument:

#![allow(unused)]
fn main() {
extern crate sigil_stitch;
use sigil_stitch::prelude::*;
let tuple = TypeName::application(
    TypeName::primitive("std::tuple"),
    vec![TypeArgument::Expansion { pattern: TypeName::parameter("Ts") }],
);
// C++: std::tuple<Ts...>
}

An empty application argument sequence is valid shared data. A language may express it, as C++ does with Bundle<>, or reject it. The library performs no arity inference or pack evaluation. The older Generic representation remains a compatibility input with its existing fields.

Wrap a base type with type parameters:

extern crate sigil_stitch;
use sigil_stitch::prelude::*;
fn main() {
// TypeScript: Promise<User>
let promise = TypeName::application(TypeName::primitive("Promise"), vec![TypeArgument::Single(TypeName::importable("./models", "User"))]);

// Rust: HashMap<String, Vec<User>>
let map = TypeName::application(TypeName::primitive("HashMap"), vec![TypeArgument::Single(TypeName::primitive("String")), TypeArgument::Single(TypeName::application(TypeName::primitive("Vec"), vec![TypeArgument::Single(TypeName::primitive("User"))]))]);
}

Nesting works to any depth. Imports are collected recursively – every Importable type anywhere in the tree gets tracked.

Union and intersection types

extern crate sigil_stitch;
use sigil_stitch::prelude::*;
fn main() {
// TypeScript: string | number | boolean
let u = TypeName::union(vec![
    TypeName::primitive("string"),
    TypeName::primitive("number"),
    TypeName::primitive("boolean"),
]);

// TypeScript: Serializable & Loggable
let i = TypeName::intersection(vec![
    TypeName::primitive("Serializable"),
    TypeName::primitive("Loggable"),
]);
}

These are primarily useful for languages with union or intersection type syntax. Each adapter must preserve the requested meaning exactly or reject the complete type; it cannot substitute a merely similar construct.

Optional types

extern crate sigil_stitch;
use sigil_stitch::prelude::*;
fn main() {
// TypeScript: string | null
// Rust:       Option<String>
// Go:         *string
// Kotlin:     String?
// Swift:      String?
let opt = TypeName::optional(TypeName::primitive("string"));
}

The selected language lowerer owns the complete optional-type grammar and rejects the variant when the target has no exact representation.

Pointer and reference types

extern crate sigil_stitch;
use sigil_stitch::prelude::*;
fn main() {
// Go: *User
let ptr = TypeName::pointer(TypeName::primitive("User"));

// Rust: &str
let r = TypeName::reference(TypeName::primitive("str"));

// Rust: &mut Vec<i32>
let rm = TypeName::reference_mut(TypeName::primitive("Vec<i32>"));
}

Reference rendering is language-aware:

  • Rust: &T / &mut T
  • C++: const T& / T&
  • C: const T* / T*
  • Go: shared reference is a no-op, mutable reference renders as *T
  • TypeScript: references are a no-op (everything is by reference)

Function types

TypeName::callable carries an ordered sequence whose scalar slots can be required or optional. Repetition supplies an element type; expansion supplies a complete pattern. Labels and ordering rules belong to the target adapter:

#![allow(unused)]
fn main() {
extern crate sigil_stitch;
use sigil_stitch::prelude::*;
let callback = TypeName::callable(
    vec![CallableParam::Single {
        name: Some("value".into()),
        type_name: TypeName::primitive("string"),
        presence: CallableParamPresence::Optional,
    }],
    TypeName::primitive("void"),
);
// TypeScript: (value?: string) => void
}

Optional presence differs from TypeName::Optional, which describes the value in a supplied slot. A target must preserve all supplied labels and segment intent or reject the complete expression; it cannot silently drop them. The older Function representation remains a compatibility input.

extern crate sigil_stitch;
use sigil_stitch::prelude::*;
fn main() {
// TypeScript: (string, number) => boolean
// Rust:       fn(String, i32) -> bool
// Python:     Callable[[str, int], bool]
// C++:        std::function<bool(string, int)>
// Dart:       bool Function(String, int)
let f = TypeName::callable(vec![CallableParam::Single { name: None, type_name: TypeName::primitive("string"), presence: CallableParamPresence::Required }, CallableParam::Single { name: None, type_name: TypeName::primitive("number"), presence: CallableParamPresence::Required }], TypeName::primitive("boolean"));
}

Function type grammar varies significantly across languages. The selected adapter owns the complete construct, including parameter order, delimiters, arrows or keywords, wrapping, and any target-derived imports.

String literal types

0.7 adds one focused singleton type:

TypeName::StringLiteral("active".to_owned())

The stored string is the decoded semantic value, not source text with quotes or escapes. Use TypeName::string_literal(...) when constructing one. TypeScript lowers it to a string literal type, Python lowers it through structured typing.Literal, and targets without an exact string singleton type reject it.

Python lowers one singleton as typing.Literal["active"]. A non-empty direct union containing only string singletons becomes one typing.Literal[...] in the original order, including duplicate members. A mixed union or a union nested inside another type lowers recursively through ordinary Python union grammar; this special case does not flatten nested unions.

Several accepted values use ordinary union composition:

TypeName::Union([
    TypeName::StringLiteral("active".to_owned()),
    TypeName::StringLiteral("inactive".to_owned()),
])

There is no separate string-enum or literal-set type. Numeric literal types are not part of this extension.

Raw escape hatch

For type expressions not covered by the built-in variants:

extern crate sigil_stitch;
use sigil_stitch::prelude::*;
fn main() {
let t = TypeName::raw("keyof User");
}

Raw emits the string verbatim with no import tracking. Use it sparingly – prefer the structured variants when possible.

Language-owned lowering across targets

The same TypeName variant lowers differently per language. Each adapter constructs a non-empty CodeBlock for the complete accepted type expression; the core validates that block, collects its imports, resolves aliases, and then uses the ordinary direct or pretty renderer. Type blocks are produced after source rewrite and are not rewritten a second time.

TypeNameTypeScriptRustGoC++
array(T)T[]Vec<T>[]Tstd::vector<T>
optional(T)T | nullOption<T>*Tstd::optional<T>
tuple(A, B)[A, B](A, B)n/astd::tuple<A, B>
reference(T)T&TTconst T&
reference_mut(T)T&mut T*TT&
map(K, V)Record<K, V>HashMap<K, V>map[K]Vstd::map<K, V>
function(A) -> R(A) => Rfn(A) -> Rfunc(A) Rstd::function<R(A)>

See TypeName Validation and Lowering for ownership, output validation, compatibility, and import behavior.

Inspection methods

extern crate sigil_stitch;
use sigil_stitch::prelude::*;
fn main() {
// Check if a type renders to empty string (used internally by ParameterSpec)
let empty = TypeName::primitive("");
assert!(empty.is_empty());

// Get the simple name (for import resolution lookups)
let t = TypeName::importable("./models", "User");
assert_eq!(t.simple_name(), Some("User"));
}