Implement abstract classes - #3
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
cf42ec7 to
998bf60
Compare
f15c512 to
1c4ff1e
Compare
998bf60 to
59c4bd9
Compare
1c4ff1e to
d755cfd
Compare
b6129c3 to
0abbbcb
Compare
d755cfd to
4d2d914
Compare
0abbbcb to
9af9e46
Compare
aab5377 to
f0da94c
Compare
9af9e46 to
bbd649e
Compare
f0da94c to
ae69e75
Compare
bbd649e to
2a1f136
Compare
ae69e75 to
0b95499
Compare
a66d3fb to
860b7f9
Compare
0b95499 to
51dcab6
Compare
860b7f9 to
4d64604
Compare
51dcab6 to
2dae200
Compare
2dae200 to
b2735fd
Compare
4d64604 to
f744212
Compare
a60ddb0 to
a294201
Compare
66131d6 to
d460f28
Compare
d460f28 to
8cdff7b
Compare
4a9414e to
d850751
Compare
8cdff7b to
4498a9a
Compare
d850751 to
8b250d5
Compare
4498a9a to
853d9c4
Compare
fdc6412 to
90a8907
Compare
|
|
||
| # Restores the original `.new` implementation for the direct subclasses of an abstract class. | ||
| #: (Class[AbstractClass]) -> void | ||
| def inherited(subclass) # :nodoc: |
There was a problem hiding this comment.
Relying on this inherited method means we only restore .new when user-defined self.inherited hooks call super. If an abstract class defines an inherited hook without super, concrete subclasses keep the abstract .new and can't be instantiated:
class Base
abstract!
def self.inherited(_subclass)
# no super
end
abstract def call; end
end
class Impl < Base
def call; end
end
Impl.new # raises CannotInstantiateAbstractClassErrorI wonder if we should install this as a prepended singleton hook, or otherwise wrap/chain the existing hook, and add a regression test so subclass instantiation doesn't depend on user hooks calling super.
There was a problem hiding this comment.
Good find. I'll come back to this, because:
- Diagnosing and raising in that case requires more state variables, which an upstack branch will have and make trivial
- Fixing it requires a bunch more anonymous modules and prepending stuff which is icky and I want to punt on.
It's doable, but not calling super is a skill issue, as the kids would say. There's even a default cop called Lint/MissingSuper which catches it.
For now I just added a test case that documents the broken behaviour.
Tracked in #45
90a8907 to
df6aaea
Compare
df6aaea to
943d60b
Compare
| # typed: strict | ||
| # frozen_string_literal: true | ||
|
|
||
| class Class |
There was a problem hiding this comment.
@amomchilov is it meant to be required standalone?
require "type_toolkit/ext/class"like we do for not_nil!
require "type_toolkit/ext/nil_assertions"If yes 2 comments:
- We should require
lib/type_toolkit/abstract_class.rbin there to avoid undefined constant - We should rename it as
type_toolkit/ext/abstract.
I'm not a fan of requiring files under ext directly, it would be cleaner to require features and let them require the internal plumbing properly:
require "type_toolkit/abstract"
require "type_toolkit/nil_assertions"There was a problem hiding this comment.
See https://github.com/Shopify/type_toolkit#cherry-picking-features
- True.
- Conventionally,
extfiles are named after the class they extend, not what they do. See https://github.com/rails/rails/tree/main/activesupport/lib/active_support/core_ext for example
42fe7ab to
02a3678
Compare
02a3678 to
4dfac15
Compare

This PR is already pretty large and complicated, so I descoped several things that would make it even worse:
Class methods like
abstract def self.fooabstract!on the singleton class, like:There are tests to ensure those unsupported features raise. I'll open follow-up PRs which implement them.