feat: don't require 'out' on expand_template (#798)

In a lot of cases the name of the generated file is unimportant.
For example in https://github.com/bazel-contrib/rules_oci/pull/534 I wanted to remove 'out' in a bunch of these calls.
diff --git a/docs/expand_template.md b/docs/expand_template.md
index e59911d..a2c1b27 100644
--- a/docs/expand_template.md
+++ b/docs/expand_template.md
@@ -31,7 +31,7 @@
 | <a id="expand_template_rule-name"></a>name |  A unique name for this target.   | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required |  |
 | <a id="expand_template_rule-data"></a>data |  List of targets for additional lookup information.   | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | <code>[]</code> |
 | <a id="expand_template_rule-is_executable"></a>is_executable |  Whether to mark the output file as executable.   | Boolean | optional | <code>False</code> |
-| <a id="expand_template_rule-out"></a>out |  Where to write the expanded file.<br><br>            If unset, the template must be a source file and the output file             will be named the same as the template file and outputted to the same             workspace-relative path. In this case there will be no pre-declared             label for the output file. It can be referenced by the target label             instead. This pattern is similar to <code>copy_to_bin</code> but with substitutions on             the copy.   | <a href="https://bazel.build/concepts/labels">Label</a> | optional |  |
+| <a id="expand_template_rule-out"></a>out |  Where to write the expanded file.<br><br>            If the <code>template</code> is a source file, then <code>out</code> defaults to             be named the same as the template file and outputted to the same             workspace-relative path. In this case there will be no pre-declared             label for the output file. It can be referenced by the target label             instead. This pattern is similar to <code>copy_to_bin</code> but with substitutions on             the copy.<br><br>            Otherwise, <code>out</code> defaults to <code>[name].txt</code>.   | <a href="https://bazel.build/concepts/labels">Label</a> | optional |  |
 | <a id="expand_template_rule-stamp"></a>stamp |  Whether to encode build information into the output. Possible values:<br><br>    - <code>stamp = 1</code>: Always stamp the build information into the output, even in         [--nostamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) builds.         This setting should be avoided, since it is non-deterministic.         It potentially causes remote cache misses for the target and         any downstream actions that depend on the result.     - <code>stamp = 0</code>: Never stamp, instead replace build information by constant values.         This gives good build result caching.     - <code>stamp = -1</code>: Embedding of build information is controlled by the         [--[no]stamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) flag.         Stamped targets are not rebuilt unless their dependencies change.   | Integer | optional | <code>-1</code> |
 | <a id="expand_template_rule-stamp_substitutions"></a>stamp_substitutions |  Mapping of strings to substitutions.<br><br>            There are overlayed on top of substitutions when stamping is enabled             for the target.<br><br>            Substitutions can contain $(execpath :target) and $(rootpath :target)             expansions, $(MAKEVAR) expansions and {{STAMP_VAR}} expansions when             stamping is enabled for the target.   | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> String</a> | optional | <code>{}</code> |
 | <a id="expand_template_rule-substitutions"></a>substitutions |  Mapping of strings to substitutions.<br><br>            Substitutions can contain $(execpath :target) and $(rootpath :target)             expansions, $(MAKEVAR) expansions and {{STAMP_VAR}} expansions when             stamping is enabled for the target.   | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> String</a> | optional | <code>{}</code> |
diff --git a/lib/private/expand_template.bzl b/lib/private/expand_template.bzl
index a98426b..64167b0 100644
--- a/lib/private/expand_template.bzl
+++ b/lib/private/expand_template.bzl
@@ -17,9 +17,10 @@
 def _expand_template_impl(ctx):
     output = ctx.outputs.out
     if not output:
-        if not ctx.file.template or not ctx.file.template.is_source:
-            fail("Template must be a source file if out is not specified")
-        output = ctx.actions.declare_file(ctx.file.template.basename, sibling = ctx.file.template)
+        if ctx.file.template and ctx.file.template.is_source:
+            output = ctx.actions.declare_file(ctx.file.template.basename, sibling = ctx.file.template)
+        else:
+            output = ctx.actions.declare_file(ctx.attr.name + ".txt")
 
     substitutions = _expand_substitutions(ctx, output, ctx.attr.substitutions)
     expand_template_info = ctx.toolchains["@aspect_bazel_lib//lib:expand_template_toolchain_type"].expand_template_info
@@ -90,12 +91,15 @@
         "out": attr.output(
             doc = """Where to write the expanded file.
 
-            If unset, the template must be a source file and the output file
-            will be named the same as the template file and outputted to the same
+            If the `template` is a source file, then `out` defaults to
+            be named the same as the template file and outputted to the same
             workspace-relative path. In this case there will be no pre-declared
             label for the output file. It can be referenced by the target label
             instead. This pattern is similar to `copy_to_bin` but with substitutions on
-            the copy.""",
+            the copy.
+
+            Otherwise, `out` defaults to `[name].txt`.
+            """,
         ),
         "stamp_substitutions": attr.string_dict(
             doc = """Mapping of strings to substitutions.
diff --git a/lib/tests/expand_template/BUILD.bazel b/lib/tests/expand_template/BUILD.bazel
index ff64a98..3d22c86 100644
--- a/lib/tests/expand_template/BUILD.bazel
+++ b/lib/tests/expand_template/BUILD.bazel
@@ -59,9 +59,9 @@
     expected = "WORKSPACE:",
 )
 
+# No `out` specified, because we don't care what the name of the generated file is.
 expand_template(
     name = "inline_template",
-    out = "inline.txt",
     substitutions = {"line2": "line3"},
     template = [
         "line1",
@@ -71,7 +71,7 @@
 
 assert_contains(
     name = "inline_template_test",
-    actual = ":inline.txt",
+    actual = ":inline_template",
     expected = "line3",
 )