Feature Manifests
Feature behavior is declared in feature.toml files living under package feature folders. Those files are not standalone: the package root morph.toml must list them (usually via [include] globs such as features/*/feature.toml). Without that edge in the manifest graph, the host will not treat the folder as part of the composed plugin.
For how those files sit next to block.toml, [build], and the ABI, read Plugin-governed pipeline first—especially the Inside packages section with real paths.
Canonical locations
Feature declarations (typical):
morphs/<Package>/features/<group-or-feature>/feature.toml
Syntax-only surfaces often live next to them as blocks (separate manifests):
morphs/<Package>/blocks/<block>/block.toml
Many packages include both patterns from the root manifest, for example Core:
[include]
files = ["features/*/feature.toml", "blocks/*/block.toml"]
Some packages only own features (GPU includes features/*/feature.toml but implements GPU syntax mainly from [build]-listed sources). Always open the owning package’s morph.toml—it is the source of truth for what is loaded.
Why feature manifests exist
- Discovery: The framework finds lowering components, routes, built-ins, and tooling without hardcoded registration tables in
src/. - Ownership: The file lives next to the C++ that implements the feature; reviewers see “what” and “where” in one tree.
- Composition: Exports, provider imports, and runtime families in the same
morph.tomldescribe how this feature participates in the cross-package graph (LLVM host, GPU provider, etc.).
What a feature manifest can declare (conceptual)
There is no single schema for every feature.toml. Sections are domain-specific, but you will repeatedly see:
| Concern | Typical manifest keys / sections | Notes |
|---|---|---|
| Built-in call | Top-level [core.input], [core.print], … | symbol, component / component_class, source, returns, builtin_family |
| Backend route | Nested [<feature>.route.hostllvm] (or other host) | provider_id, route_id, artifact_kind, required_extensions, source for emit |
| REPL / IDE behavior | [<feature>.repl], chain methods like [<feature>.chain.min] | component_class, source |
| Binary / unary ops | [operation.add] in packages like Ops | nir_kind, token_request_id, vm_dispatch_id, precedence, semantic_family |
| Tooling | Sections consumed by CLI/LSP codegen | Declared under a feature folder and listed in [build] |
The Morph ABI (MorphABI.h) names roles and phases (semantic, lowering, backend route, …). The manifest + generated glue map your C++ factories onto those roles.
Concrete patterns in the tree (read these files)
morphs/Core/features/api/feature.toml— built-ins (Input,Print, …) with[core.*.route.hostllvm]subsections tying each toprovider.core.host_llvmand LLVM emit sources.morphs/Ops/features/operations/feature.toml—[operation.*]entries linking lexer token requests, NIR opcode kinds, and VM dispatch ids.morphs/Flow/features/...— control-flow and flow-specific features (open the folder list; each subfolder may carry its ownfeature.tomlper[include]).
Syntax forms parallel to features:
morphs/Flow/blocks/branch_block/block.toml—[forms.if_statement]with aruleDSL,produces, andsource(grammar-only manifest shape).
If you add a new statement form, you usually touch block.toml; if you add a built-in, route, or op, you usually touch feature.toml—and you always update morph.toml [include] and [build] when new C++ must compile into the package library.
Implementation files: not optional
A feature.toml source field points at a concrete .cpp (or path) that implements the registered component. The root [build] section lists nir_sources, sema_sources, codegen_sources, cli_sources, etc., so those translation units are built into morphs/<Package>/bin/... morph library.
Rule of thumb: if the C++ is not on a manifest-driven list, assume it is dead or test-only until you wire it.
Discovery is manifest-driven
Morph does not treat a random .cpp under morphs/ as a feature hook just because it exists.
Correct order when adding behavior:
- Pick the owning package under
morphs/<Package>/. - Add or extend
feature.toml(and/orblock.toml). - Ensure
morph.toml[include]covers the file. - Add
[build]sources and rebuild withmorphc(see Playbook).
A common mistake
Do not add an implementation file and stop there.
If the manifest graph does not declare the feature, the framework cannot register it, and the change drifts toward core-first patches. Likewise, do not copy a fictional minimal [feature] table from old docs—open a real package and mirror its section style.
Relation to project morph.toml (user repo)
The morph.toml at the project root (dependencies, targets, [modulecpp], …) is documented under Learn Morph → Toolchain → morph.toml. This page is about morphs/<Package>/morph.toml and nested feature.toml / block.toml inside language packages, not the end-user project file—though both use TOML, they answer different questions (application build vs compiler plugin graph).
Next steps
- Plugin-governed pipeline — ABI, hosts, and a concrete manifest tour
- Package layout — folders under
morphs/<Package>/ - morph.toml (framework) — root package manifest keys
- Creating methods and chain features
- Providers and artifacts
- Feature kinds
- Extending existing packages