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 perhapssuperclassesorbase_classescould be a better name.