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’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support merging doc comments of macro generated items #14039
Comments
I fear this might be adding too much complexity while offering a not very flexible solution. Whould it be possible to apply the doc comment concatenation inside the macro? macro gen_method(name, *, comment)
# {{ comment.id }}
#
# Comment added via macro
def {{name.id}}
end
end
gen_method foo, comment: "Comment on macro call" If we had access to a macro's |
It would be a possible workaround yea, just wouldn't be all that natural from the end users perspective tho. Especially when the comment can have newlines, code fences, etc.
Even if we had this, wouldn't it be blocked by the outcome of #7273? Which last I read thru was mostly 👎. |
I think this could serve as a reasonable use case for #7273. The negative comments in that issue revolved around the presented use case. And the fact that docs are only available in docs mode which wouldn't be an issue here because it's explicitly used for docs mode. |
The macro's caller context would be something new as well yea? Were you thinking that would be a new meta var like |
Yeah, something like that. I wasn't thinking about a specific implementation though, just the general concept. |
Okay, I got something working via: diff --git a/src/compiler/crystal/macros/interpreter.cr b/src/compiler/crystal/macros/interpreter.cr
index d9f5473ec..5b53f6067 100644
--- a/src/compiler/crystal/macros/interpreter.cr
+++ b/src/compiler/crystal/macros/interpreter.cr
@@ -69,7 +69,7 @@ module Crystal
vars[macro_block_arg.name] = call_block || Nop.new
end
- new(program, scope, path_lookup, a_macro.location, vars, call.block, a_def, in_macro)
+ new(program, scope, path_lookup, a_macro.location, vars, call.block, a_def, in_macro, call)
end
record MacroVarKey, name : String, exps : Array(ASTNode)?
@@ -77,7 +77,7 @@ module Crystal
def initialize(@program : Program,
@scope : Type, @path_lookup : Type, @location : Location?,
@vars = {} of String => ASTNode, @block : Block? = nil, @def : Def? = nil,
- @in_macro = false)
+ @in_macro = false, @call : Call? = nil)
@str = IO::Memory.new(512) # Can't be String::Builder because of `{{debug}}`
@last = Nop.new
end
@@ -540,6 +540,8 @@ module Crystal
@last = TypeNode.new(@program)
when "@def"
@last = @def || NilLiteral.new
+ when "@caller"
+ @last = @call || NilLiteral.new
else
node.raise "unknown macro instance var: '#{node.name}'"
end
diff --git a/src/compiler/crystal/macros/methods.cr b/src/compiler/crystal/macros/methods.cr
index 90c52fa60..f27824e4f 100644
--- a/src/compiler/crystal/macros/methods.cr
+++ b/src/compiler/crystal/macros/methods.cr
@@ -400,6 +400,8 @@ module Crystal
interpret_check_args { symbolize }
when "class_name"
interpret_check_args { class_name }
+ when "docs"
+ interpret_check_args { StringLiteral.new(self.doc || "") }
when "raise"
macro_raise self, args, interpreter, Crystal::MacroRaiseException
when "warning" With the test code: macro def_method(name)
# {{ @caller.docs.id }}
#
# I am a macro comment
def {{name.id}} : String
"foo"
end
end
# Macro call comment
def_method test
# I am a def comment
def foo
{{ @def.docs }}
end But I'm starting to see some issues/limitations with this approach, as it stands at least.
Code in test.cr:15:3
15 | def_method test
^
Called macro defined in test.cr:1:1
1 | macro def_method(name)
Which expanded to:
> 1 | # Macro call comment:
> 2 | ```
^
Error: unexpected token: "DELIMITER_START" Probably because each line isn't going to have the Was thinking we could prepend the character to all lines after the first? Should be fine given even if you did like Something like: interpret_check_args do
doc_lines = (self.doc || "").lines
doc_lines.each_with_index do |line, idx|
# Assume the first line will always inherently have a `#` prefix when expanded
next if idx.zero?
doc_lines[idx] = "# #{line}"
end
StringLiteral.new doc_lines.join '\n'
end We could also require the extra lines to be escaped directly in the call docs: # Macro call comment:
# # ```
# # puts "hello!"
# # ```
def_method test but that isn't a great UX. |
|
Ah nice. Even if the calling macro is going to need to do this pretty much every time? Could maybe add a named argument to |
This issue has been mentioned on Crystal Forum. There might be relevant details there: https://forum.crystal-lang.org/t/next-level-documentation/6564/1 |
Say you have a macro that defines something that is displayed in the generated API docs, a method for example:
In this example the doc comments on the method definition and macro invocation are mutually exclusive; with the docs added within the macro itself taking precedence:
This is ultimately due to this logic:
crystal/src/compiler/crystal/semantic/semantic_visitor.cr
Line 382 in 123de81
I propose that there should be a way to support both, such that you can have macro generated docs, but then provide additional documentation when calling said macro.
A naive approach could be just do something like
node.doc = "#{@doc}\n\n#{node.doc}".lstrip
. A more robust approach could be the introduction of a new doc directive that will gsub the invocation docs where ever that token is located in the macro definition docs. We can't reuse:inherit:
due to that probably causing conflicts if the method generated via the macro happens to override the same method in a parent type.The text was updated successfully, but these errors were encountered: