# Interfaces

An interface names a set of members that any type carrying them satisfies. The
declaration emits nothing at run time unless it supplies a default
implementation.

```nupp:playground
local interface Named
    name: string
end

local record Circle
    name: string
    radius: number
end

local n: Named = new Circle(name = "c", radius = 1)
```

## Satisfaction is structural

A type satisfies an interface by carrying its members. No declaration is
required, and a plain table shape works the same way:

```nupp:fragment
local n: Named = {name = "anonymous"}
```

An optional member may be left out only where the value is known not to carry
it: a fresh table literal, a record, a struct, or a module's own table. Any other
shape may be a narrowed view of a value that holds the member under another
type, so it has to declare the member to fit:

```nupp:refused
local type Labelled = {@readonly label: string?}

local full = {id = 1, label = 5}
local narrowed: {id: integer} = full
local fresh: Labelled = {id = 2} -- a fresh literal carries no label
local view: Labelled = narrowed -- NUPP2001: narrowed may hold a label of another type
```

A member the interface implements with a body counts as one of its members
here. The body is installed only on a declaration that names the interface
with `is`, so a shape or an undeclared record has to carry that member itself
to satisfy the interface, and one that lacks it is refused rather than left to
find nothing at the call.

Members and indexers may be declared `@readonly` and `@writeonly` independently,
which controls both access and variance. See [properties.md](properties.md) for
what each capability admits.

## Sealed interfaces

`sealed` limits structural satisfaction to the declaring module:

```nupp
sealed interface span.Span<T>
    @readonly count: integer
    get: function(self: Span<T>, index: integer): T
end
```

Only types declared in the same module may satisfy that contract. Their members
follow the ordinary interface compatibility rules. A type in another module is
rejected even when it has the same fields. The owning module can keep the
representation private and export constructors returning the interface.

Sealing is entirely static. It emits no tag, wrapper, virtual dispatch, or
runtime test. It earns its place when the visible methods rely on facts that
mere field shape cannot prove, such as a pointer agreeing with a count. An `any`
value remains unchecked and can cross the boundary with the usual loss of static
guarantees.

When comparing member functions that both declare a receiver -- a leading
`self`, or a first parameter typed as the declaring type -- the receiver is
skipped on both sides, since each implementation names it for itself. A member
without one is a plain callable field, and its first parameter is compared like
any other: a plain field does not satisfy a method member, whose receiver would
land in that parameter.

## `is` is a claim, not a proof

```nupp:fragment
local record Tagged is Named
    name: string
    weight: number
end
```

`is` does two things. It inherits the parent's members and metamethods, with
`self` rebound from the parent to the child. And it declares satisfaction.

What that declaration is trusted for is exactly the members the record does not
write. A record declaring `is Component` satisfies `Component` even if a runtime
registrar has not installed the members yet. It is the same trust boundary as a
declaration file or an FFI signature. If nothing ever installs them, the program
still fails at run time.

Every member the record does write is held to the parent's, at the declaration,
and a conflict is NUPP2118:

- A field or method has to fit the parent's member. `name: integer` does not
  satisfy `name: string`, and `describe: integer` does not satisfy a method.
  The comparison is covariant: a record may narrow a parent's field, which is
  how a tagged interface refines `kind: string` to `kind: "circle"`, and a
  method is compared the way any implementation is, with the receiver skipped
  and a narrower result allowed.
- A generic parent is compared as instantiated. `record StrSink is Sink<integer>`
  cannot declare `push(self, v: string)`.
- A capability the record withholds is withheld. `@readonly value` beside a parent
  whose `value` is writable is a conflict, not a field the parent's write entry
  quietly reopens, and the record's own writes to it are refused as well.
- A stored field cannot stand where the parent provides a default method,
  whatever its type: on an instance the field would shadow the default.
  Replacing a default is written as a method with `@override`.
- An `Affine` parent's cleanup takes its receiver, so implementing
  `close(takes self)` as `close(self)` is refused.
- A method implementing a member the parent declared `@nosuspend function` may
  not suspend. A method's own guarantee is inferred from its body, so this is
  checked once the file's effects have settled, and only a proven suspension is
  reported: a body whose calls cannot be followed keeps the claim, as a bodyless
  declaration would. An `@nosuspend` region asks more of what it calls, and says so
  at the call.

A struct is held to one more thing. Its layout is closed, so a parent member no
struct field can hold, such as `name: string`, cannot be installed later either,
and the claim is refused where a record's would be trusted.

Only interfaces may be named after `is`, and anything else is reported. An
interface cannot name itself, directly or through a cycle of other interfaces.
Multiple parents are allowed:

```nupp:fragment
local record Task is Named, Callable
end
```

## Bounded generics

An interface is the usual bound for a type parameter:

```nupp:fragment
local function start<T is Callable>(task: T): T
    return task()
end
```

Inside the body, fields, methods, and metamethods are read from the bound, and
`self` specializes back to the type parameter. Bounds are checked where a
generic is instantiated, not inside the subtyping relation. See
[generics.md](generics.md#constraints-use-is) for inference and the rest of the
bound rules.

## `is` at runtime

The `is` operator tests a value's type:

```nupp:fragment
if shape is Circle then
    print(shape.radius)
end
```

It compiles for `nil`, the primitives, function types, records, and structs. A
record reaches its declaration through `__index`, so a value the declaration
built answers yes whether it was stamped directly or linked back to; a struct
uses `ffi.istype`. `x is integer` compiles to `type(x) == "number"`, because
integrality is not checked at run time.

### Tests that do not run

A test the subject's own type settles does not run at all:

```nupp:fragment
local maybe: Shape? = pick()
if maybe is Shape then -- compiles to `maybe ~= nil`
    use(maybe)
end
```

`c is Shape` where `c` is a `Circle` and `Circle is Shape` is `true` by the
declaration, and an optional's nil is the only part left to ask. This works
whatever the interface can or cannot test at run time, and it is the usual
reason `is` against an interface succeeds.

Because it trusts the type, an `is` in the tail of an exhaustive chain over a
union is answered by what the earlier branches ruled out rather than re-checked.
That is the same trust the checker already extends when it lets a branch read
the narrowed type's fields without a test, so a value that reached the union
through an `as` it did not deserve is answered by the cast, not by `is`.

### Tags an interface already declares

Where the subject's type does not settle it, an interface has no runtime table
of its own. An interface whose fields carry literal types has already said what
its test is, since the field admits that value and nothing else:

::: code-group
```nupp [Nupp]
local interface Circle
    kind: "circle"
    radius: number
end
```

```lua [Generated Lua]
-- `x is Circle` becomes
(type(x) == "table" and x.kind == "circle")
```
:::

That is what lets a decoded table answer `is`. It applies to interfaces only: a
record and a struct already answer exactly, so a derived test beside either
would be a second answer chosen by whether the fields happened to be literals.

### Refinement tests

A [`satisfies` declaration](refinements.md) states the test outright, and it
wins over the tags when both are present. With neither, and against an alias,
there is nothing to test, and code generation refuses it.

::: deepdive
An interface emits no runtime table, so `is` against one is answered by static
elision, by a tag its own literal-typed fields already declare, or by a
`satisfies` test it names. Registering conformance on every declaration was
designed and verified, then declined: elision and tags already answered every
case except an untagged interface against a subject whose type does not prove
it, which is one case weighed against a runtime table on every interface in the
language.
:::

## Default implementations

An interface may implement what it declares, and a declaration that takes the
contract takes the behavior with it:

::: code-group
```nupp [Nupp]
local interface Greeter
    name: string

    function greet(self): string
        return "hello, " .. self.name
    end
end

local record Person is Greeter
    name: string
end
```

```lua [Generated Lua]
const Greeter = {}
function Greeter.greet(self)
return "hello, " .. self.name
end

const Person = {} Person.__index = Person Person.greet = Greeter.greet
```
:::

The body is emitted **once** and referenced, not copied. It is resolved where
the implementor is written rather than looked up at run time, so there is no
chain and no indirection. A struct takes it through its metatype's index table,
and a chain of interfaces passes it along.

This is the one thing that gives an interface a runtime presence. An interface
declaring only signatures still emits nothing at all, so the table is paid for
by the feature rather than by every interface. It is also why an interface
carrying defaults has to be reachable from an implementor in another module.

Two interfaces providing the same name is refused. They are two implementations
and no reason to prefer either, so the declaration writes the member itself to
say which behavior it means.

### Explicit overrides

`@override` may mark a method satisfying any interface requirement, including
a bodyless one. It reports an error when the method matches no requirement:

```nupp:fragment
local record Shouter is Greeter
    name: string

    @override
    function greet(self): string
        return "HELLO, " .. self.name
    end
end
```

That catches a misspelled name that would define a new method. The annotation
is optional when the implementation satisfies the requirement.

A child interface can also mark a method body with `@override` when the parent
declared only a signature. The replacement is checked against the inherited
signature, with ordinary parameter and result variance:

```nupp
local interface Named
    render: function(self, prefix: string): string
end

local interface Labeled is Named
    @override
    function render(self, prefix: string): "label"
        return "label"
    end
end
```

For an overloaded default, replacement is matched by parameter pack rather than
source name alone. Each repeated method body is a separate entry, so a record
may `@override` one signature and continue inheriting the others, and no
`@overload` annotation is needed because repeated names form the overload set.
See [overloads.md](overloads.md#default-implementations-and-override) for
complete examples, including bodyless contracts and defaults contributed by
separate interfaces.

## Metamethod contracts

An interface or record may declare how an operator behaves on it:

```nupp
local interface Component
    componentName: string
    metamethod __call: function(self, ...: any): self
end

local record Position is Component
    x: number
    y: number
end
```

The declaration is a static contract. It emits no `__call` field, builds no
metatable, and decides nothing about what the call constructs. Ordinary Lua code
installs the function with `setmetatable`, a registrar, or a foreign runtime.
Inheriting the contract rebinds `self`, so `Position(...)` has type `Position`.

`__eq` and `__tostring` participate in conformance but do not change a result
type: `==` is always `boolean` and `tostring` is always `string`. When the left
operand has no contract, the checker consults the right one, matching Lua's
fallback direction, and the declared parameters still describe the actual left
and right values, so a right-hand-only contract has to type both positions. See
[metamethods.md](../metamethods.md#supported-contracts) for the
declarable set and the parameters each one takes.

Structs cannot declare metamethod contracts, because LuaJIT metatypes must be
installed when `ffi.metatype` is called and an erased promise would leave no
later fulfillment point. The bit-operator contracts (`__band`, `__bor`,
`__bxor`, `__bnot`, `__shl`, `__shr`, `__sar`) and `__idiv` are rejected because
the LuaJIT 2.1 backport does not dispatch them. `__gc` and `__close` are not
contracts either, since deterministic cleanup is
[ownership](../../runtime/ownership/borrowing.md).

## FAQ

### When is an interface better than a record?

When several unrelated types must answer the same contract, or when a module
wants to publish a shape without publishing its representation. A concrete
record API needs no interface merely to call its own methods. See [records and
structs](records-and-structs.md#choosing) for which representation a type wants.

### Why does `is` against an interface usually compile to nothing?

Because the subject's declared type has already answered it. An interface has no
runtime table, so the checker elides the test wherever the type proves it, and
falls back to a literal-field tag or a `satisfies` refinement where it does not.

### Can an interface add fields to the types that satisfy it?

No. An interface declares members and, optionally, default implementations for
its own methods, but satisfaction is a fact about the implementor's own members.
A default implementation is one shared function referenced from the implementor,
not a field copied into it.

::: seealso
- [refinements.md](refinements.md) for the `satisfies` test an interface may
  carry
- [properties.md](properties.md) for `@readonly` and `@writeonly` members
- [metamethods.md](../metamethods.md) for generic indexing, runtime
  fulfillment, `metatable<T>`, and the full set of exclusions
- [overloads.md](overloads.md#default-implementations-and-override) for
  `@override` against an overload set
:::
