# Toon — Smart Inference

When no `ToonDescriptionAttribute` is present, Toon auto-generates description, purpose, and constraints by inspecting property names and types. Implemented in `Serializers/Toons/AcToonSerializer.Descriptions.cs`.

Inference **fills the `@types` section**. It has no effect on `@data` values — those serialize by runtime reflection regardless.

## Fallback chains

Per-field, the first non-empty value wins:

| Field | Priority order |
|---|---|
| `description` | `ToonDescription.Description` (with placeholders resolved) → `System.ComponentModel.[Description]` → inferred-from-name → `null` (line skipped). |
| `purpose` | `ToonDescription.Purpose` (with placeholders) → inferred-from-name → empty (line skipped). |
| `constraints` | `ToonDescription.Constraints` (merge if placeholder, else replace) → MS DataAnnotations (`[Required]`, `[Range]`, `[EmailAddress]`, etc.) → type-derived small-int ranges → name-based inference → post-process append of `readonly` / `not-mapped` / `enum-type`. **Redundancy-conscious**: no auto `nullable` / `required` — the type hint already communicates this. |
| `examples` | `ToonDescription.Examples` (placeholders resolved) → empty (no auto-fallback). |

Attribute precedence details: `TOON_ATTRIBUTES.md`.

## Name-based description patterns

| Property name pattern | Description emitted |
|---|---|
| `Id` (exact, case-insensitive) | `Unique identifier for {TypeName}` |
| `Name` (exact) | `Name of the {TypeName}` |
| contains `Email` | `Email address` |
| contains `Phone` | `Phone number` |
| contains `Address` | `Physical or mailing address` |
| contains `Date` OR type is `DateTime`/`DateTime?` | `Date/time value for {PropertyName}` |
| starts with `Is` + `bool` | `Boolean flag indicating {rest after "Is"}` |
| starts with `Has` + `bool` | `Boolean flag indicating possession of {rest after "Has"}` |
| ends with `Count` + integer type | `Count of {rest before "Count"}` |
| collection (element type known, not `object`) | `Collection of {ElementType} for {TypeName}` |
| dictionary | `Dictionary mapping for {PropertyName} in {TypeName}` |
| fallback | `Property {name} of type {baseType}[ (nullable)]` |

Matching is case-insensitive where noted and uses `string.Contains` or `StartsWith` as indicated.

## Name-based purpose patterns

| Property name pattern | Purpose emitted |
|---|---|
| `Id` (exact) | `Primary key / unique identification` |
| contains `CreatedAt` | `Timestamp when entity was created` |
| contains `UpdatedAt` OR `ModifiedAt` | `Timestamp of last update` |
| contains `DeletedAt` | `Soft delete timestamp` |
| starts with `Is` | `Status flag` |
| contains `Version` | `Version tracking / concurrency control` |
| (fallback) | empty (line skipped) |

## Type-derived and name-based constraints

Constraints join with `, `. Inference is **redundancy-conscious** — Toon skips constraints already implied by the type hint (`PropertyName: typeHint`) to save LLM tokens. Source: inline comments in `AcToonSerializer.AttributeExtraction.cs` → `ExtractTypeConstraints`.

### Not emitted automatically

- **`nullable` / `required`** — type hint conveys this (`int?` vs `int`, `DateTime?` vs `DateTime`, reference vs non-nullable value type). `required` IS emitted when an explicit `[Required]` DataAnnotation is present — signals intent beyond the type.
- **Large-range numeric bounds** — no auto range on `int`, `long`, `ulong`, `float`, `double`, `decimal`. Type name already bounds the value; `range: -2147483648-2147483647` would be noise.
- **Enum property constraints** — none. The `@types` enum definition holds the name ↔ numeric value map.

### Emitted automatically — small-integer ranges

Useful extra info because LLMs can misjudge narrow integer type sizes:

| Type | Added constraint |
|---|---|
| `byte` | `range: 0-255` |
| `sbyte` | `range: -128-127` |
| `short` (`int16`) | `range: -32768-32767` |
| `ushort` (`uint16`) | `range: 0-65535` |
| `uint` (`uint32`) | `range: 0-4294967295` |

### Emitted automatically — name-based additions

| Condition | Appended constraint |
|---|---|
| `string` + name contains `Email` | `email-format` |
| `string` + name contains `Url` | `url-format` |
| integer type + name contains `Age` | `range: 0-150` |
| integer type + name ends with `Count` | `non-negative` |
| property has `[NotMappedAttribute]` or `[NotColumnAttribute]` | `not-mapped` |
| read-only property (no public setter) | `readonly` |
| enum backing field detected (see below) | `enum-type: {EnumName}` |

> ⚠️ **`Age` pattern false positives** — the rule uses `string.Contains("Age", OrdinalIgnoreCase)`, which is not word-boundary-aware. Hits `LanguageId`, `Package*`, `Image*`, `Page*`, `Message*`, `Storage*` and similar. See `TOON_ISSUES.md#accore-toon-i-x3h2` for detected cases and fix options. Lesser risk applies to the `Email`, `Phone`, `Address`, `Url`, `Date`, `Version`, `CreatedAt`, `UpdatedAt`, `DeletedAt` patterns too.

## Enum backing field detection

Special pass in `AcToonSerializer.MetaWriter.cs`.

Trigger conditions — all must hold on the same declaring type:
- An **enum property** (e.g. `Status`) marked with `[NotMapped]` / `[NotColumn]` / `[JsonIgnore]`.
- A **backing property** named `{EnumPropertyName}Id` (e.g. `StatusId`).
- The backing property's type matches the enum's underlying type (`int`/`int?`, `byte`/`byte?`, etc.).

Effects:
- The enum type is collected and documented in `@types`.
- The backing `StatusId` property's constraint list gains `enum-type: Status`.

This supports the common "store enum as int in DB, expose as enum on the model" pattern without requiring manual annotation.

## What Toon does NOT infer

- **Ranges beyond `Age`** — no `[Range]` equivalent inferred from `Percent`, `Rating`, `Score`, etc.
- **Length limits** — no `max-length` inferred from `Name`, `Description`.
- **Uniqueness** — never inferred without `[ToonDescription(Constraints = "unique")]`.
- **Business logic** — `BusinessRule` requires explicit `[ToonDescription]`.

For anything beyond the catalogs above, use `ToonDescriptionAttribute` — see `TOON_ATTRIBUTES.md`.
