Summary#
A few places in Osy# take a value that has to be known while the app is being compiled, not while it is running — an attribute argument, a setting in a config block, an enum member's display label, a workflow message. Those places require a constant.
A string literal is the obvious constant. So is a chain of string literals joined with +: the compiler folds
"Awaiting " + "review" into "Awaiting review" before it checks that the value is constant, exactly as C# does.
That means a long sentence can be split across lines for readability without the compiler objecting.
What is not constant is still refused, and for the real reason. A value that depends on a member, a parameter or a runtime call is not known at compile time, so it cannot go where a constant is required — no matter how it is spelled.
Signature#
[Label("Awaiting " + "review")] // folded → "Awaiting review"
[Label("Signed " + "off " + "by legal")] // any length of chain folds
[Pattern("^DOC-" + Code)] // REFUSED — `Code` is not a constant
[Pattern($"^DOC-[0-9]+$")] // REFUSED — an interpolated string is not a literalDescription#
Where a constant is required#
These are the places that read a value at compile time rather than evaluating it at run time:
- Attribute arguments —
[Label],[Pattern],[ExternalName], and a constraint's optional message. - Enum member labels —
[Label]and[Icon]on a member. - Config settings — the values inside a config block, and the entries of a string list.
- Workflow messages — a terminal's or a requirement's
Message. - Agent and tool descriptions — a
Descriptionon a tool or agent, which the model reads.
In each case the value is baked into the application's metadata when it is compiled. There is no later moment at which a non-constant could be evaluated, which is why the requirement exists.
Folding#
Folding is left-nested, mirroring how + associates: "a" + "b" + "c" folds only because each step folds. A chain
with one non-constant operand is not a constant at all, and returns nothing rather than a partial result — so
"Hello " + name is refused rather than quietly becoming "Hello ".
Folding happens during the compile, not in the parsed source. Tooling that reads your code — hover, go-to-definition,
selection — still sees the expression you wrote, with the + intact.
Interpolated strings are deliberately not constants#
$"…" is refused where a constant is required, even when it happens to contain no holes. The $ prefix declares an
intent to interpolate, and a place that needs a compile-time value should say so plainly rather than accept a form
whose purpose is to be computed. Write a plain literal, or a + chain of them.
Examples#
A long label split across two lines, and a three-part chain:
enum ReviewState {
[Label("Awaiting " + "review")] Pending,
[Label("Signed " + "off " + "by legal")] Approved,
}An attribute whose pattern and whose failure message are both split:
entity Doc {
[Pattern("^DOC-" + "[0-9]+$", "use " + "DOC-1234")] string Code;
ReviewState State;
}Neither of these is constant, and both are refused:
[Pattern("^DOC-" + Code)] // depends on a member — not known at compile time
[Pattern($"^DOC-[0-9]+$")] // an interpolated string is not a literalSee also#
- Optional and required members — how a member's type spelling decides whether a value must be supplied
- constraints — the attributes that take a pattern and a message
- enum — where member labels are declared