Creating Methods And Chain Features
One of the first real plugin-author tasks is adding a new method or chain method to an existing Morph-owned feature.
This is already how the repo models things such as:
Input<T>.Min(...)Input<T>.Max(...)Input<T>.Default(...)Tensor.Random(...)
A Real Manifest Example
From the current Core package, a chain method is declared like this:
[core.input.chain.min]
method = "Min"
source = "features/Input/chain/Min.cpp"
component_class = "core.input.chain.min"
param_minValue = ["t"]
returns = "Self"
This is not just documentation.
It tells the framework:
- the method name exposed to the language
- the source file that implements the semantic chain behavior
- the component class that generated glue and registries will resolve
- the parameter contract
- the return contract
Another Real Example
From the Tensor package:
[tensor.random.chain.random]
method = "Random"
source = "features/Tensor/chain/Random.cpp"
component_class = "tensor.chain.random"
param_rows = ["int"]
param_cols = ["int"]
returns = "Self"
This is how a package-owned method gets declared without editing a core method table.
The Implementation Side
The matching implementation for Input<T>.Min(...) lives in package space:
#include "../InputFeaturePlugin.h"
namespace morph::nir::morph {
namespace {
using morph::morph::ChainSemanticFeature;
using morph::morph::ChainSignatureView;
using morph::morph::SemanticFeatureContext;
using morph::morph::FeatureSegmentView;
using core_input::InputFeatureSemanticFact;
class InputMinChainSemantic final : public ChainSemanticFeature {
public:
bool apply(SemanticFeatureContext &ctx,
const morph::morph::FeatureInvocationView &,
const FeatureSegmentView &segment,
const ChainSignatureView &signature,
morph::morph::FeatureSemanticFact &fact,
std::string *outError) override {
InputFeatureSemanticFact *inputFact =
core_input::asInputFeatureSemanticFact(fact);
if (inputFact == nullptr || inputFact->inputType == nullptr) {
if (outError != nullptr) {
*outError =
"Input<T>.Min() received an incompatible featureInvocation fact.";
}
return false;
}
if (!inputFact->inputType->isNumeric()) {
if (outError != nullptr) {
*outError = "Input<T>.Min() is only valid for numeric T.";
}
return false;
}
if (!core_input::expectChainArity(segment, signature, outError)) {
return false;
}
ASTNode *argNode = core_input::singleArgument(segment);
NTypePtr argType = ctx.infer(argNode);
if (argType != nullptr && !argType->isError() && !argType->isUnknown() &&
!argType->isDynamic() && !argType->isNumeric()) {
if (outError != nullptr) {
*outError = "Input<T>.Min() argument must be numeric.";
}
return false;
}
inputFact->minArg = argNode;
return true;
}
};
} // namespace
MORPHLANG_MORPH_DEFINE_CHAIN_SEMANTIC_FACTORY(core_input_chain_min,
InputMinChainSemantic)
} // namespace morph::nir::morph
What This Code Is Doing
This file shows the real pattern:
- inherit from
ChainSemanticFeature - validate the incoming fact type
- validate the receiver state
- validate arity and argument types
- write accepted information back into the feature fact
- export the factory with a Morph SDK macro
Nothing here requires a core semantic switch table.
How To Add A New Method
If you want to add a new chain method:
- choose the owning package
- add a new section to the package feature manifest
- implement a package-local semantic class like the example above
- export it with the matching factory macro
- update tests for arity, typing, and valid usage
The package owns the method from manifest to implementation.
What Not To Do
Do not:
- patch a core method map
- add one-off method parsing to generic parser code
- encode package-specific method behavior in unrelated sema files
The method belongs to the package feature graph.
Next Steps
- Semantic And Lowering Walkthrough - how a package feature moves from accepted syntax to lowered IR
- Platform-Specific Lowering - how one feature can lower differently per route