Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow getting a TypeNode's name without type vars #8483

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
60 changes: 56 additions & 4 deletions spec/compiler/macro/macro_methods_spec.cr
Expand Up @@ -11,7 +11,7 @@ private def declare_class_var(container : ClassVarContainer, name, var_type : Ty
end

module Crystal
describe "macro methods" do
describe Macro do
describe "node methods" do
describe "location" do
location = Location.new("foo.cr", 1, 2)
Expand Down Expand Up @@ -1212,9 +1212,61 @@ module Crystal
end
end

it "executes name" do
assert_macro("x", "{{x.name}}", "String") do |program|
[TypeNode.new(program.string)] of ASTNode
describe "#name" do
describe "simple type" do
it "returns the name of the type" do
assert_macro("x", "{{x.name}}", "String") do |program|
[TypeNode.new(program.string)] of ASTNode
end
end
end

describe "namespaced type" do
it "should return the FQN of the type" do
assert_macro("type", "{{type.name}}", "SomeModule::SomeType") do |program|
mod = NonGenericModuleType.new(program, program, "SomeModule")

klass = NonGenericClassType.new(program, mod, "SomeType", program.reference)

[TypeNode.new(klass)] of ASTNode
end
end
end

describe "generic type" do
it "includes the generic_args of the type by default" do
assert_macro("klass", "{{klass.name}}", "SomeType(A, B)") do |program|
[TypeNode.new(GenericClassType.new(program, program, "SomeType", program.object, ["A", "B"]))] of ASTNode
end
end
end

describe :generic_args do
describe true do
it "includes the generic_args of the type" do
assert_macro("klass", "{{klass.name(generic_args: true)}}", "SomeType(A, B)") do |program|
[TypeNode.new(GenericClassType.new(program, program, "SomeType", program.object, ["A", "B"]))] of ASTNode
end
end
end

describe false do
it "does not include the generic_args of the type" do
assert_macro("klass", "{{klass.name(generic_args: false)}}", "SomeType") do |program|
[TypeNode.new(GenericClassType.new(program, program, "SomeType", program.object, ["A", "B"]))] of ASTNode
end
end
end

describe "with an invalid type argument" do
it "should raise the proper exception" do
expect_raises(Crystal::TypeException, "named argument 'generic_args' to TypeNode#name must be a bool, not NumberLiteral") do
assert_macro("x", "{{x.name(generic_args: 99)}}", "String") do |program|
[TypeNode.new(program.string)] of ASTNode
end
end
end
end
end
end

Expand Down
14 changes: 12 additions & 2 deletions src/compiler/crystal/macros.cr
Expand Up @@ -1725,8 +1725,18 @@ module Crystal::Macros
def union_types : ArrayLiteral(TypeNode)
end

# Returns the fully qualified name of this type.
def name : MacroId
# Returns the fully qualified name of this type. Optionally without *generic_args* if `self` is a generic type; see `#type_vars`.
#
# ```
# class Foo(T); end
#
# module Bar::Baz; end
#
# {{Bar::Baz.name}} # => Bar::Baz
# {{Foo.name}} # => Foo(T)
# {{Foo.name(generic_args: false)}} # => Foo
# ```
def name(*, generic_args : BoolLiteral = true) : MacroId
end

# Returns the type variables of the generic type. If the type is not
Expand Down
12 changes: 11 additions & 1 deletion src/compiler/crystal/macros/methods.cr
Expand Up @@ -1519,7 +1519,17 @@ module Crystal
when "union_types"
interpret_argless_method(method, args) { TypeNode.union_types(self) }
when "name"
interpret_argless_method(method, args) { MacroId.new(type.devirtualize.to_s) }
interpret_argless_method(method, args) do
generic_args = if named_args && (generic_arg = named_args["generic_args"]?)
generic_arg
else
BoolLiteral.new true
end

raise "named argument 'generic_args' to TypeNode#name must be a bool, not #{generic_args.class_desc}" unless generic_args.is_a?(BoolLiteral)

MacroId.new(type.devirtualize.to_s(generic_args: generic_args.value))
end
when "type_vars"
interpret_argless_method(method, args) { TypeNode.type_vars(type) }
when "instance_vars"
Expand Down
6 changes: 6 additions & 0 deletions src/compiler/crystal/types.cr
Expand Up @@ -719,6 +719,12 @@ module Crystal
nil
end

def to_s(*, generic_args : Bool = true)
String.build do |io|
to_s_with_options io, generic_args: generic_args
end
end

def inspect(io : IO) : Nil
to_s(io)
end
Expand Down