Skip to content

Implementation inheritance for classes#209

Open
skberkeley wants to merge 1 commit into
masterfrom
skberkeley/implementation-inheritance
Open

Implementation inheritance for classes#209
skberkeley wants to merge 1 commit into
masterfrom
skberkeley/implementation-inheritance

Conversation

@skberkeley

@skberkeley skberkeley commented Jun 4, 2026

Copy link
Copy Markdown

@skberkeley skberkeley self-assigned this Jun 4, 2026
@skberkeley skberkeley marked this pull request as ready for review June 4, 2026 17:42
@TenebrisNoctua

Copy link
Copy Markdown

Pretty decent RFC, although I wonder how will the relationship between abstract classes and interfaces will be, if they're both implemented. I think the only difference would be that a class can implement multiple interfaces, while a class can only inherit from one abstract class.

That aside, I want to ask the possibility of a protected keyword. If private defines a field only accessible from class methods, protected could define a field that is only accessible from class methods and subclasses. Though this may introduce additional overwriting behavior.

Comment on lines +93 to +108
The original class RFC states that class names are hoisted and so can be used before the class declaration has been evaluated. This is unchanged by this RFC, but carries with it a consequence that is important to call out:

Class declarations have effects at the top level. Therefore, a class cannot inherit from another class that occurs lexically after it within the module.

```
class Child extends Base -- error: Base is nil here!
end

class Base
end

class SecondChild extends Base -- OK
end
```

Developers are advised to order their class declarations accordingly. We will likely need a lint rule to detect this case.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, a good example of why hoisting is a terrible idea. This is a textbook TDZ in JavaScript. The small ergonomic gain hoisting brings is squashed by unintuitive restrictions like these. It would be both simpler and more robust to just not hoist class identifiers at all.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I heavily agree. It just feels like a weird workaround for a promised optimization opportunity.

Comment on lines +215 to +223
Adding implementation inheritance reduces optimization opportunities involving class method inlining and dispatch. For example, consider the following:

```
function callToString(a: Animal)
return a.__tostring()
end
```

Since `Animal` has been subclassed by `Cat`, we can no longer statically determine which method to dispatch within the body of `callToString`. To address this, we may need a `final` keyword preventing classes from being subclassed.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a huge drawback that renders a bunch of decisions made in the previous RFC pointless (e.g. classes only allowed at top-level, no __index metamethods, no constructor protocol). If the plan was to drop those performance constraints all along, they shouldn't have been tailoring the discussion to begin with.

I was under the impression that the team was interested in a system similar to Rust's traits/interfaces that would be more optimization friendly. Why was this scrapped?

@Bottersnike

Bottersnike commented Jun 4, 2026

Copy link
Copy Markdown

Subclasses are forbidden from redeclaring fields declared in their superclasses

I'm not convinced redeclaring a field with an identical type should be forbidden. While allowing either subtypes or supertypes would be unsound for mutable fields, an identical redeclaration is probably harmless and may improve readability when documenting a subclass's complete public surface. That said, I'm not sure the additional complexity is justified so up in the air about this one.

As a side note, I think some of the wording could be improved a little. This RFC talks about "methods" but then does things like Child.foo()/c.foo(). While pedantic, these are more akin to static methods than normal methods, though I appreciate luau doesn't formally distinguish between these. Updating examples to use c:foo() and have self parameters might be clearer? I would I think also demonstrate that (Child{}):foo() works even when the base class was function foo(self: Base).

The example provided in one of the drawbacks confuses me a little:

function callToString(a: Animal)
	return a.__tostring()
end

This might be totally valid code--my memory of the exact class semantics is admittedly not 100%--but metamethods are usually not directly accesible on a table (or instance in this case)?

We acknowledge the lack of a self type, which is necessary for methods that need to refer to the current class type even for subclasses. (eg to write a function clone(self): self ... end) This can be proposed in another RFC.

This feels like a huge problem. I agree it's not this RFC's problem to solve, and I love the ideas that this RFC proposes, but this feels like a big problem that needs solved sooner rather than later. The more work that goes into classes that revolves around "if an argument comes first, and is named self, we kinda just ignore the type" the messier things are going to get once everything eventually needs cleaned up.

Class declarations have effects at the top level. Therefore, a class cannot inherit from another class that occurs lexically after it within the module.

I understand this, and do think it makes absolute sense (and somewhat avoids cyclic classes [though I worry if the cyclic requires may allow cyclic inheritance chains?]), but with how magic hoisting is I think this more draws attention to the problems presented by hoisting. That's not a strike against this RFC from me or even against this particular semantic, but more a general concern of where we're heading.

@Bottersnike Bottersnike mentioned this pull request Jun 5, 2026
@Bottersnike

Bottersnike commented Jun 5, 2026

Copy link
Copy Markdown

In this RFC, it's stated that:

Methods defined on base classes are accessible via the derived class.

In the original classes RFC, it's stated that:

Classes can define the following Luau metamethods. They all work just like they do on a metatable:
[list omitted]

Does inheritance also inherit "metamethods"? Are they even metamethods, or just share names with metamethods?

Both this and #210 call metamethod-shaped methods in ways that would normally not be possible for metamethods, so I'm not even sure if they're "methods" or "metamethods". (Akin perhaps to __foo__ functions in python classes, that have special meaning but no special treatment?)

If inheritance does inherit metamethods, which I think would be nice, do we allow Foo + ChildOfFoo or only Foo + Foo and ChildOfFoo + ChildOfFoo (as with metamethods at current, which notably can't be inherited via __index). If a child has an override for, in this example, __add, when doing Foo + ChildOfFoo who's do we use? Or do we only allow it if __add was inherited unchanged?

@Cooldude2606

Copy link
Copy Markdown

If inheritance does inherit metamethods, which I think would be nice, do we allow Foo + ChildOfFoo or only Foo + Foo and ChildOfFoo + ChildOfFoo (as with metamethods at current, which notably can't be inherited via __index). If a child has an override for, in this example, __add, when doing Foo + ChildOfFoo who's do we use? Or do we only allow it if __add was inherited unchanged?

Lua 5.1, and therefore I assume also Luau, already defines how __add would function if the two values have different metatables. According the the Lua 5.1 reference the left hand side is first checked for a metatable, if present then it is used, otherwise the right hand side is checked. Therefore, I would expect that in the case of Foo + ChildOfFoo it would be Foo.__add(Foo, ChildOfFoo) while if it was ChildOfFoo + Foo then it would use ChildOfFoo.__add(ChildOfFoo, Foo). For classes to work differently to this would be inconsistent.

Also I agree it would be nice for metamethods to be inherited, and also overridable following the same logic as any other class method. I also generally agree that this is a good RFC but the comments highlight reason why now might not be the best time to move forward with this, and instead we need to resolve issues with self typing, hoisting, and the loss of optimisation potentinal promissed by classes. Only being able to extend abstract classes sounds like a great proposal for maintaining general optimisation potentinal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants