Skip to content

A type implementing traits cannot retain its own methods in current trait convention #8

@Word30210

Description

@Word30210

Summary

First, check tests/std/trait/.check.luau on the main branch to see the current trait conventions.

When a class implements one or more traits using trait.impl, its prototype must be typed as __index = Class :: {} to avoid an ambiguous-call error caused by intersection types. As a side effect, any method defined directly on the class — one not required by any trait — becomes invisible to the type system on instances of that class.

Background

The trait system in src/std/trait/init.luau defines a trait through three pieces:

  • Requires<Self> — the shape of required methods,
  • Defaults — a table of default-method implementations,
  • For<Self>Requires<Self> & Defaults.

A class that implements a trait declares its instance type as an intersection of its own shape with each For<Self>:

export type Box<T> = setmetatable<
    { read value: T },
    typeof(BoxPrototype)
> & Display.For<Box<T>> & Copyable.For<Box<T>>

If BoxPrototype.__index is typed as the class table directly (__index = Box), each required method appears twice in the final intersection — once from the class table (the implementer redefines it) and once from Requires<Self>. Luau then treats them as overloaded function types and refuses to dispatch:

Calling function ... is ambiguous

The current workaround is to widen __index to {} at its declaration site:

local BoxPrototype = table.freeze({
    __index = Box :: {} -- erase the class table's type to avoid the duplicate
})

This silences the ambiguity, but only because Box's own methods are no longer reachable through typeof(BoxPrototype).

The problem

The workaround holds as long as the class defines only methods that are required by some trait it implements. The moment the class adds a method of its own, the type system cannot see it on instances:

function Box.unpack<T>(self: Box<T>): T
    return self.value
end

local b = Box.new(42)
b:unpack() -- type error: unknown property `unpack` on `Box<number>`

The method exists at runtime — the underlying table has it — but Box<T>'s type no longer mentions unpack, because typeof(BoxPrototype)'s __index was widened to {}.

Reproduction

Write a class that implements any trait(s) (e.g. simple Display trait) yourself and implement its own method and try to call it.

Expected behavior

A class should be able to expose its own methods alongside the methods required by the traits it implements, and those methods should be visible on the instance type.

Metadata

Metadata

Assignees

Labels

bug/typeSome type isn't working

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions