# Narrowing a model-defined type

[Narrowing](../narrowing.md) › named types. The names in the examples are those of [section 2](../narrowing.md#2-the-names-in-the-examples).

A model-defined type is a structural alias, not a nominal tag. A scope can declare `m` as `Person`, where `Person` resolves to `context<age: number, name: string>`. `Feel.Type.resolve/2` resolves aliases recursively against `scope.types`; narrowing then uses the resulting context type and [context rules](context.md). The value does not need to have been constructed by that model.

| Expression | Type | Warnings |
| --- | --- | --- |
| `m` | `context<age: number \| Null, name: string \| Null> \| Null` | — |
| `m.age` | `number \| Null` | — |
| `if m.age > 5 then m else null` | `context<age: number in (5..max], name: string \| Null> \| Null` | — |
| `if m != null then m else null` | `context<age: number \| Null, name: string \| Null> \| Null` | — |
| `m instance of Person` | `boolean` | — |
| `{age: 1, name: "a"} instance of Person` | `boolean = true` | — |
| `if v instance of Person then v else null` | `context<age: number \| Null, name: string \| Null> \| Null` | — |

`instance of` checks the resolved structural type. Item-definition allowed values and type constraints are separate checks, not part of that type test. They can narrow a bound variable when its declaration policy permits it ([structural constraints](structure.md)). With a valid target type, `instance of` returns a boolean; null is an instance of `Null` only.

## Missing and recursive definitions

An unresolved type in a variable declaration is an analysis error when that declaration is read. An invalid target of `instance of` is different: preparation keeps a warning. Runtime reports an error and yields null in lenient mode, which can select the else branch of a surrounding `if`; strict execution stops at the error.

| Expression | Type | Warnings |
| --- | --- | --- |
| `1 instance of Missing` | `Null` | `invalid_type_expression` |
| `if 1 instance of Missing then 1 else 2` | `number = 2` | `invalid_type_expression` |

`Feel.Type.resolve/2` returns `{:error, {:cyclic_type, name}}` for a cycle of unresolved named references. `Feel.ItemDefinitions.new/2` rejects recursive definitions by default, but `recursive: :guarded` produces finite graph descriptors that the resolver keeps. Runtime membership checks full finite values; declaration boundaries also enforce their descendant constraints. Analysis sees a bounded structural upper approximation. DMN selects guarded mode with its `:recursion` feature. FEEL's legacy `recursive: :any` instead cuts back references and does not provide complete descendant validation.

An upper projection cannot prove an `instance of` test true, remove its check, or exclude the projection from the negative branch. Conversion can change a value even if it fits the projection, so recursive parameter/result boundaries use post-conversion bounds. Collection heads and aliases are unfolded for singleton wrapping and element allowed values. See [the guarded-type contract](../type-system.md#guarded-recursive-types) for supported cycles, function signatures and conservative type relations.

## Preserve the type scope when reusing source

Resolution erases a type's name from the IR, and folding can erase the predicate altogether. The original source still needs its type definitions when it is prepared again. With `N` defined as `number`, `if 1 instance of N then 1 else 2` returns `1`; without `N`, its invalid test returns null and the expression returns `2` with an error in lenient mode. Even parsing can change: a known type named `number and false` must be read as one name, not a built-in type followed by a boolean operator.

DMN preserves each unit's named types, including imported names, in the `types: map` option to `Feel.Rules` queries. This applies to explicit entries and wildcard `inputValues`, even when the prepared IR no longer records the original reference. Value bindings, filter fields and extension calls have a different contract: unsupported dependencies stay unknown rather than being reinterpreted outside their scope. See the [table-proof contract](../type-system.md#56-what-the-types-are-used-for).

## Unresolved internal alternatives

`Feel.Type.Union.from_spec/1` can represent an unresolved `{:named, name}` alternative. Readers that encounter it must stay conservative: facts do not inspect an unavailable definition and operations can fall back to `Any | Null`. Ordinary prepared variable declarations resolve names before those facts are used; this fallback is not a substitute for supplying a valid scope.

See also: [context](context.md), [structure](structure.md); `Feel.ItemDefinitions` reads model item definitions into a scope (D-99).
