# Constant expressions

> Some places take a value that must be known at compile time — an attribute argument, a config setting, a workflow message. Adjacent string literals joined with `+` are folded before that check, so you can split a long sentence across lines. Anything that is not constant is still refused.

<!-- id: types-constant-expressions · area: types · stability: stable · html: https://osysharp.com/reference/types/constant-expressions/ -->

## Summary        {#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      {#signature}

```osy syntax
[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 literal
```

## Description    {#description}

### Where a constant is required   {#where-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 `Description` on 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}

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   {#interpolation}

`$"…"` 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       {#examples}

A long label split across two lines, and a three-part chain:

```osy title="splitting a long label" test app=types-constant-expressions
enum ReviewState {
  [Label("Awaiting " + "review")] Pending,
  [Label("Signed " + "off " + "by legal")] Approved,
}
```

An attribute whose pattern *and* whose failure message are both split:

```osy title="a constraint written across lines" test app=types-constant-expressions
entity Doc {
  [Pattern("^DOC-" + "[0-9]+$", "use " + "DOC-1234")] string Code;
  ReviewState State;
}
```

Neither of these is constant, and both are refused:

```osy title="✗ a member reference, and an interpolation" syntax
[Pattern("^DOC-" + Code)]     // depends on a member — not known at compile time
[Pattern($"^DOC-[0-9]+$")]    // an interpolated string is not a literal
```

## See also       {#see-also}
- [Optional and required members](https://osysharp.com/reference/types/optional-and-required/) — how a member's type spelling decides whether a value must be supplied
- [constraints](https://osysharp.com/reference/entity/constraints/) — the attributes that take a pattern and a message
- [enum](https://osysharp.com/reference/enum/declaration/) — where member labels are declared
