From 8741abd5793784a47b1210e585a15954659e20b1 Mon Sep 17 00:00:00 2001 From: George Dietrich Date: Thu, 27 Nov 2025 14:49:49 -0500 Subject: [PATCH] Allow overriding `def_clone` created `#clone` method docs --- .../crystal/tools/doc/generator_spec.cr | 45 +++++++++++++++++++ src/object.cr | 4 ++ 2 files changed, 49 insertions(+) diff --git a/spec/compiler/crystal/tools/doc/generator_spec.cr b/spec/compiler/crystal/tools/doc/generator_spec.cr index a8a8e704be79..a28b81b3cb35 100644 --- a/spec/compiler/crystal/tools/doc/generator_spec.cr +++ b/spec/compiler/crystal/tools/doc/generator_spec.cr @@ -307,4 +307,49 @@ describe Doc::Generator do XML end + + describe "macro generating method with conditional docs via @caller.first.doc_comment" do + it "uses default doc when caller has no doc comment" do + program = top_level_semantic(<<-CRYSTAL, wants_doc: true).program + macro gen_method + {% if @caller && !@caller.first.doc_comment.empty? %} + # {{ @caller.first.doc_comment }} + {% else %} + # Default documentation. + {% end %} + def foo + end + end + + class Foo + gen_method + end + CRYSTAL + generator = Doc::Generator.new program, [""] + method = generator.type(program.types["Foo"]).lookup_method("foo").not_nil! + method.doc.should eq("Default documentation.") + end + + it "uses caller doc when provided" do + program = top_level_semantic(<<-CRYSTAL, wants_doc: true).program + macro gen_method + {% if @caller && !@caller.first.doc_comment.empty? %} + # {{ @caller.first.doc_comment }} + {% else %} + # Default documentation. + {% end %} + def foo + end + end + + class Foo + # Custom documentation. + gen_method + end + CRYSTAL + generator = Doc::Generator.new program, [""] + method = generator.type(program.types["Foo"]).lookup_method("foo").not_nil! + method.doc.should eq("Custom documentation.") + end + end end diff --git a/src/object.cr b/src/object.cr index 4290b23668db..998fe9df4c59 100644 --- a/src/object.cr +++ b/src/object.cr @@ -678,7 +678,11 @@ class Object # Defines a `clone` method that returns a copy of this object with all # instance variables cloned (`clone` is in turn invoked on them). macro def_clone + {% if @caller && !@caller.first.doc_comment.empty? %} + # {{ @caller.first.doc_comment }} + {% else %} # Returns a copy of `self` with all instance variables cloned. + {% end %} def clone \{% if @type < ::Reference && !@type.instance_vars.map(&.type).all? { |t| t == ::Bool || t == ::Char || t == ::Symbol || t == ::String || t < ::Number::Primitive } %} exec_recursive_clone do |hash|