It's currently possible to create an array of subclasses in a macro:
class Hierarchy
def self.subclasses
{{@type.all_subclasses}} # using all_subclasses for illustration
end
end
class A < Hierarchy; end
class B < A; end
class C < B; end
puts A.subclasses # [B,C]
It would be nice to also have a similar method for superclasses:
class Hierarchy
def self.superclasses
{{@type.superclasses}} #superclasses doesn't currently exist
end
end
puts C.superclasses # [B, A, Hierarchy], as proposed
Is there any way to do this currently? I can't seem to figure out a way to leverage @type.superclass to walk up the class hierarchy.
Like this? (needs some patch)
class Foo
def self.ancestors
{{@type.ancestors}}
end
end
class A < Foo; end
class B < A; end
puts B.ancestors # [A, Foo, Reference, Class]
I don't know if this is necessary for Crystal, if yes, the git-formatted-patch below can be just simply applied:
From a44a6319238df017fe88df32e9e805a2109f8f7f Mon Sep 17 00:00:00 2001
From: David Kuo <[email protected]>
Date: Wed, 11 Jan 2017 17:18:26 +0800
Subject: [PATCH] [Compiler] Available getting ancestors in macro
---
src/compiler/crystal/macros/methods.cr | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr
index 7ce4646..fb459ed 100644
--- a/src/compiler/crystal/macros/methods.cr
+++ b/src/compiler/crystal/macros/methods.cr
@@ -1295,6 +1295,8 @@ module Crystal
case method
when "abstract?"
interpret_argless_method(method, args) { BoolLiteral.new(type.abstract?) }
+ when "ancestors"
+ interpret_argless_method(method, args) { TypeNode.ancestors(type) }
when "union?"
interpret_argless_method(method, args) { BoolLiteral.new(type.is_a?(UnionType)) }
when "union_types"
@@ -1464,6 +1466,10 @@ module Crystal
NilLiteral.new
end
+ def self.ancestors(type)
+ ArrayLiteral.map(type.ancestors) { |ancestor| TypeNode.new(ancestor) }
+ end
+
def self.subclasses(type)
ArrayLiteral.map(type.subclasses) { |subtype| TypeNode.new(subtype) }
end
--
2.7.4
Yes, that's perfect!
Looks nice! Could you send a PR with this, including some specs (covering generic classes as well as plain classes) and docs? We'll be happy to merge it.
One thing to discuss: though I like the name ancestors
, it has a slightly different semantic in Ruby, where it also returns the included modules. This could cause some confusion for people coming from the Ruby world, so perhaps superclasses
or base_classes
could be a better name.
I agree withe the name of #superclasses since there's also a #subclasses @type method.
@spalladino Type#ancestors
acts as Ruby's (both include modules), only the different is Ruby includes itself (because of the Module#prepend
)
# Crystal:
module FooBar; end
class Foo; end
class Bar < Foo
include FooBar
end
puts Bar.ancestors # [FooBar, Foo, Reference, Class]
# Ruby
module FooBar; end
class Foo; end
class Bar < Foo
include FooBar
end
puts Bar.ancestors # [Bar, FooBar, Foo, Object, Kernel, BasicObject]
module FooBar2; end
Bar.send :prepend, FooBar2
puts Bar.ancestors # [FooBar2, Bar, FooBar, Foo, Object, Kernel, BasicObject]
I can send a PR with ancestors
and the version only shows the inherit chain (maybe named superclasses
)
Let's go for ancestors
then. This is what the compiler uses internally and the Ruby difference is minor. I prefer the Crystal behavior BTW.
My bad. I agree with @ysbaddaden then, ancestors
it is.
It seems this was implemented in #3875
Most helpful comment
Looks nice! Could you send a PR with this, including some specs (covering generic classes as well as plain classes) and docs? We'll be happy to merge it.
One thing to discuss: though I like the name
ancestors
, it has a slightly different semantic in Ruby, where it also returns the included modules. This could cause some confusion for people coming from the Ruby world, so perhapssuperclasses
orbase_classes
could be a better name.