Added `rust_unpretty_aspect` and `rust_unpretty` rules (#2356)
Proposal for https://github.com/bazelbuild/rules_rust/issues/1642
Duplicates https://github.com/bazelbuild/rules_rust/pull/1643 (special
thanks to @freeformstu)
### Summary
Rustc can be used to expand all macros so that you can inspect the
generated source files easier.
This feature is enabled via `-Zunpretty={mode}`. The `-Z` flag is only
available in the nightly
version of `rustc` (https://github.com/rust-lang/rust/issues/43364).
### Unprettying
Build and test your targets normally.
```
bazel build //:ok_binary
INFO: Analyzed target //:ok_binary (0 packages loaded, 0 targets configured).
INFO: Found 1 target...
Target //:ok_binary up-to-date:
bazel-bin/ok_binary
INFO: Elapsed time: 0.081s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
```
Use the aspect to generate the expanded files in as a one-off build.
(`.bazelrc`)
```
# Enable unpretty for all targets in the workspace
build:unpretty --aspects=@rules_rust//rust:defs.bzl%rust_unpretty_aspect
build:unpretty --output_groups=+rust_unpretty
# `unpretty` requires the nightly toolchain. See tracking issue:
# https://github.com/rust-lang/rust/issues/43364
build:unpretty --@rules_rust//rust/toolchain/channel=nightly
```
```
bazel build --config=unpretty //:ok_binary
INFO: Analyzed target //:ok_binary (1 packages loaded, 2 targets configured).
INFO: Found 1 target...
Aspect @rules_rust//rust/private:unpretty.bzl%rust_unpretty_aspect of //:ok_binary up-to-date:
bazel-bin/ok_binary.expand.rs
INFO: Elapsed time: 0.149s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
```
Targeting tests is valid as well.
```
bazel build --config=unpretty //:ok_test
INFO: Analyzed target //:ok_test (0 packages loaded, 2 targets configured).
INFO: Found 1 target...
Aspect @rules_rust//rust/private:unpretty.bzl%rust_expand_aspect of //:ok_test up-to-date:
bazel-bin/test-397521499/ok_test.expand.rs
INFO: Elapsed time: 0.113s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
```
Finally, manually wire up a `rust_unpretty` target explicitly if you
want a target to build. This rule is unique compared to the aspect in
that it forces a transition to a nightly toolchain so that `-Zunpretty`
can be used.
```starlark
load("@rules_rust//rust:defs.bzl", "rust_binary", "rust_unpretty")
rust_binary(
name = "ok_binary",
srcs = ["src/main.rs"],
edition = "2021",
)
rust_unpretty(
name = "ok_binary_expand",
deps = [":ok_binary"],
)
```
```
bazel build //:ok_binary_expand
INFO: Analyzed target //:ok_binary_expand (0 packages loaded, 1 target configured).
INFO: Found 1 target...
Target //:ok_binary_expand up-to-date:
bazel-bin/ok_binary.expand.rs
INFO: Elapsed time: 0.090s, Critical Path: 0.00s
INFO: 1 process: 1 internal.
INFO: Build completed successfully, 1 total action
```diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml
index 9d23e7b..80f88d4 100644
--- a/.bazelci/presubmit.yml
+++ b/.bazelci/presubmit.yml
@@ -15,6 +15,7 @@
- "//..."
# These tests are expected to fail as they require both a nightly and stable toolchain.
- "-//test/unit/channel_transitions/..."
+ - "-//test/unpretty/..."
default_linux_targets: &default_linux_targets
- "--"
- "//..."
@@ -169,6 +170,13 @@
test_targets: *default_windows_targets
soft_fail: yes
bazel: "rolling"
+ ubuntu2004_unpretty:
+ name: Unpretty
+ platform: ubuntu2004
+ build_targets: *default_linux_targets
+ test_targets: *default_linux_targets
+ build_flags:
+ - "--config=unpretty"
ubuntu2004_bzlmod_only:
name: With bzlmod
platform: ubuntu2004
diff --git a/.bazelrc b/.bazelrc
index 00e698c..0667486 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -32,6 +32,14 @@
build:clippy --aspects=//rust:defs.bzl%rust_clippy_aspect
build:clippy --output_groups=+clippy_checks
+# Enable unpretty for all targets in the workspace
+build:unpretty --aspects=//rust:defs.bzl%rust_unpretty_aspect
+build:unpretty --output_groups=+rust_unpretty
+
+# `unpretty` requires the nightly toolchain. See tracking issue:
+# https://github.com/rust-lang/rust/issues/43364
+build:unpretty --//rust/toolchain/channel=nightly
+
###############################################################################
## Incompatibility flags
###############################################################################
diff --git a/rust/defs.bzl b/rust/defs.bzl
index 8573048..3cee1af 100644
--- a/rust/defs.bzl
+++ b/rust/defs.bzl
@@ -65,6 +65,11 @@
_rustfmt_aspect = "rustfmt_aspect",
_rustfmt_test = "rustfmt_test",
)
+load(
+ "//rust/private:unpretty.bzl",
+ _rust_unpretty = "rust_unpretty",
+ _rust_unpretty_aspect = "rust_unpretty_aspect",
+)
rust_library = _rust_library
# See @rules_rust//rust/private:rust.bzl for a complete description.
@@ -111,6 +116,12 @@
rustc_output_diagnostics = _rustc_output_diagnostics
# See @rules_rust//rust/private:rustc.bzl for a complete description.
+rust_unpretty_aspect = _rust_unpretty_aspect
+# See @rules_rust//rust/private:unpretty.bzl for a complete description.
+
+rust_unpretty = _rust_unpretty
+# See @rules_rust//rust/private:unpretty.bzl for a complete description.
+
error_format = _error_format
# See @rules_rust//rust/private:rustc.bzl for a complete description.
diff --git a/rust/private/rust.bzl b/rust/private/rust.bzl
index 3e89d50..bed6f81 100644
--- a/rust/private/rust.bzl
+++ b/rust/private/rust.bzl
@@ -266,6 +266,22 @@
return providers
+def get_rust_test_flags(attr):
+ """Determine the desired rustc flags for test targets.
+
+ Args:
+ attr (dict): Attributes of a rule
+
+ Returns:
+ List: A list of test flags
+ """
+ if getattr(attr, "use_libtest_harness", True):
+ rust_flags = ["--test"]
+ else:
+ rust_flags = ["--cfg", "test"]
+
+ return rust_flags
+
def _rust_test_impl(ctx):
"""The implementation of the `rust_test` rule.
@@ -394,7 +410,7 @@
attr = ctx.attr,
toolchain = toolchain,
crate_info_dict = crate_info_dict,
- rust_flags = ["--test"] if ctx.attr.use_libtest_harness else ["--cfg", "test"],
+ rust_flags = get_rust_test_flags(ctx.attr),
skip_expanding_rustc_env = True,
)
data = getattr(ctx.attr, "data", [])
@@ -487,6 +503,56 @@
values = [1, 0, -1],
)
+# Internal attributes core to Rustc actions.
+RUSTC_ATTRS = {
+ "_cc_toolchain": attr.label(
+ doc = (
+ "In order to use find_cc_toolchain, your rule has to depend " +
+ "on C++ toolchain. See `@rules_cc//cc:find_cc_toolchain.bzl` " +
+ "docs for details."
+ ),
+ default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
+ ),
+ "_error_format": attr.label(
+ default = Label("//:error_format"),
+ ),
+ "_extra_exec_rustc_flag": attr.label(
+ default = Label("//:extra_exec_rustc_flag"),
+ ),
+ "_extra_exec_rustc_flags": attr.label(
+ default = Label("//:extra_exec_rustc_flags"),
+ ),
+ "_extra_rustc_flag": attr.label(
+ default = Label("//:extra_rustc_flag"),
+ ),
+ "_extra_rustc_flags": attr.label(
+ default = Label("//:extra_rustc_flags"),
+ ),
+ "_import_macro_dep": attr.label(
+ default = Label("//util/import"),
+ cfg = "exec",
+ ),
+ "_is_proc_macro_dep": attr.label(
+ default = Label("//rust/private:is_proc_macro_dep"),
+ ),
+ "_is_proc_macro_dep_enabled": attr.label(
+ default = Label("//rust/private:is_proc_macro_dep_enabled"),
+ ),
+ "_per_crate_rustc_flag": attr.label(
+ default = Label("//:experimental_per_crate_rustc_flag"),
+ ),
+ "_process_wrapper": attr.label(
+ doc = "A process wrapper for running rustc on all platforms.",
+ default = Label("//util/process_wrapper"),
+ executable = True,
+ allow_single_file = True,
+ cfg = "exec",
+ ),
+ "_rustc_output_diagnostics": attr.label(
+ default = Label("//:rustc_output_diagnostics"),
+ ),
+}
+
_common_attrs = {
"aliases": attr.label_keyed_string_dict(
doc = dedent("""\
@@ -618,62 +684,18 @@
"""),
allow_files = [".rs"],
),
- "stamp": _stamp_attribute(default_value = 0),
+ "stamp": _stamp_attribute(
+ default_value = 0,
+ ),
"version": attr.string(
doc = "A version to inject in the cargo environment variable.",
default = "0.0.0",
),
- "_cc_toolchain": attr.label(
- doc = (
- "In order to use find_cc_toolchain, your rule has to depend " +
- "on C++ toolchain. See `@rules_cc//cc:find_cc_toolchain.bzl` " +
- "docs for details."
- ),
- default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
- ),
- "_error_format": attr.label(
- default = Label("//:error_format"),
- ),
- "_extra_exec_rustc_flag": attr.label(
- default = Label("//:extra_exec_rustc_flag"),
- ),
- "_extra_exec_rustc_flags": attr.label(
- default = Label("//:extra_exec_rustc_flags"),
- ),
- "_extra_rustc_flag": attr.label(
- default = Label("//:extra_rustc_flag"),
- ),
- "_extra_rustc_flags": attr.label(
- default = Label("//:extra_rustc_flags"),
- ),
- "_import_macro_dep": attr.label(
- default = Label("//util/import"),
- cfg = "exec",
- ),
- "_is_proc_macro_dep": attr.label(
- default = Label("//rust/private:is_proc_macro_dep"),
- ),
- "_is_proc_macro_dep_enabled": attr.label(
- default = Label("//rust/private:is_proc_macro_dep_enabled"),
- ),
- "_per_crate_rustc_flag": attr.label(
- default = Label("//:experimental_per_crate_rustc_flag"),
- ),
- "_process_wrapper": attr.label(
- doc = "A process wrapper for running rustc on all platforms.",
- default = Label("//util/process_wrapper"),
- executable = True,
- allow_single_file = True,
- cfg = "exec",
- ),
- "_rustc_output_diagnostics": attr.label(
- default = Label("//:rustc_output_diagnostics"),
- ),
"_stamp_flag": attr.label(
doc = "A setting used to determine whether or not the `--stamp` flag is enabled",
default = Label("//rust/private:stamp"),
),
-}
+} | RUSTC_ATTRS
_coverage_attrs = {
"_collect_cc_coverage": attr.label(
diff --git a/rust/private/unpretty.bzl b/rust/private/unpretty.bzl
new file mode 100644
index 0000000..81b04e0
--- /dev/null
+++ b/rust/private/unpretty.bzl
@@ -0,0 +1,344 @@
+"""A module defining Rust 'unpretty' rules"""
+
+load("//rust/private:common.bzl", "rust_common")
+load(
+ "//rust/private:rust.bzl",
+ "RUSTC_ATTRS",
+ "get_rust_test_flags",
+)
+load(
+ "//rust/private:rustc.bzl",
+ "collect_deps",
+ "collect_inputs",
+ "construct_arguments",
+)
+load(
+ "//rust/private:utils.bzl",
+ "determine_output_hash",
+ "find_cc_toolchain",
+ "find_toolchain",
+)
+
+# This list is determined by running the following command:
+#
+# rustc +nightly -Zunpretty=
+#
+_UNPRETTY_MODES = [
+ "ast-tree,expanded",
+ "ast-tree",
+ "expanded,hygiene",
+ "expanded,identified",
+ "expanded",
+ "hir-tree",
+ "hir,identified",
+ "hir,typed",
+ "hir",
+ "identified",
+ "mir-cfg",
+ "mir",
+ "normal",
+]
+
+RustUnprettyInfo = provider(
+ doc = "A provider describing the Rust unpretty mode.",
+ fields = {
+ "modes": "Depset[string]: Can be any of {}".format(["'{}'".format(m) for m in _UNPRETTY_MODES]),
+ },
+)
+
+def _rust_unpretty_flag_impl(ctx):
+ value = ctx.build_setting_value
+ invalid = []
+ for mode in value:
+ if mode not in _UNPRETTY_MODES:
+ invalid.append(mode)
+ if invalid:
+ fail("{} build setting allowed to take values [{}] but was set to unallowed values: {}".format(
+ ctx.label,
+ ", ".join(["'{}'".format(m) for m in _UNPRETTY_MODES]),
+ invalid,
+ ))
+
+ return RustUnprettyInfo(modes = depset(value))
+
+rust_unpretty_flag = rule(
+ doc = "A build setting which represents the Rust unpretty mode. The allowed values are {}".format(_UNPRETTY_MODES),
+ implementation = _rust_unpretty_flag_impl,
+ build_setting = config.string_list(
+ flag = True,
+ repeatable = True,
+ ),
+)
+
+def _nightly_unpretty_transition_impl(settings, attr):
+ mode = settings[str(Label("//rust/settings:unpretty"))]
+
+ # Use the presence of _unpretty_modes as a proxy for whether this is a rust_unpretty target.
+ if hasattr(attr, "_unpretty_modes") and hasattr(attr, "mode"):
+ mode = mode + [attr.mode]
+
+ return {
+ str(Label("//rust/settings:unpretty")): depset(mode).to_list(),
+ str(Label("//rust/toolchain/channel")): "nightly",
+ }
+
+nightly_unpretty_transition = transition(
+ implementation = _nightly_unpretty_transition_impl,
+ inputs = [str(Label("//rust/settings:unpretty"))],
+ outputs = [
+ str(Label("//rust/settings:unpretty")),
+ str(Label("//rust/toolchain/channel")),
+ ],
+)
+
+def _get_unpretty_ready_crate_info(target, aspect_ctx):
+ """Check that a target is suitable for expansion and extract the `CrateInfo` provider from it.
+
+ Args:
+ target (Target): The target the aspect is running on.
+ aspect_ctx (ctx, optional): The aspect's context object.
+
+ Returns:
+ CrateInfo, optional: A `CrateInfo` provider if rust unpretty should be run or `None`.
+ """
+
+ # Ignore external targets
+ if target.label.workspace_root.startswith("external"):
+ return None
+
+ # Targets with specific tags will not be formatted
+ if aspect_ctx:
+ ignore_tags = [
+ "nounpretty",
+ "no-unpretty",
+ "no_unpretty",
+ ]
+
+ for tag in ignore_tags:
+ if tag in aspect_ctx.rule.attr.tags:
+ return None
+
+ # Obviously ignore any targets that don't contain `CrateInfo`
+ if rust_common.crate_info not in target:
+ return None
+
+ return target[rust_common.crate_info]
+
+def _rust_unpretty_aspect_impl(target, ctx):
+ crate_info = _get_unpretty_ready_crate_info(target, ctx)
+ if not crate_info:
+ return []
+
+ toolchain = find_toolchain(ctx)
+ cc_toolchain, feature_configuration = find_cc_toolchain(ctx)
+
+ dep_info, build_info, linkstamps = collect_deps(
+ deps = crate_info.deps,
+ proc_macro_deps = crate_info.proc_macro_deps,
+ aliases = crate_info.aliases,
+ # Rust expand doesn't need to invoke transitive linking, therefore doesn't need linkstamps.
+ are_linkstamps_supported = False,
+ )
+
+ compile_inputs, out_dir, build_env_files, build_flags_files, linkstamp_outs, ambiguous_libs = collect_inputs(
+ ctx,
+ ctx.rule.file,
+ ctx.rule.files,
+ linkstamps,
+ toolchain,
+ cc_toolchain,
+ feature_configuration,
+ crate_info,
+ dep_info,
+ build_info,
+ )
+
+ output_groups = {}
+ outputs = []
+
+ for mode in ctx.attr._unpretty_modes[RustUnprettyInfo].modes.to_list():
+ pretty_mode = mode.replace("-", "_")
+ mnemonic = "RustUnpretty{}".format("".join([
+ o.title()
+ for m in pretty_mode.split(",")
+ for o in m.split("_")
+ ]))
+
+ unpretty_out = ctx.actions.declare_file(
+ "{}.unpretty.{}.rs".format(ctx.label.name, pretty_mode.replace(",", ".")),
+ sibling = crate_info.output,
+ )
+
+ output_groups.update({"rust_unpretty_{}".format(pretty_mode.replace(",", "_")): depset([unpretty_out])})
+ outputs.append(unpretty_out)
+
+ rust_flags = []
+ if crate_info.is_test:
+ rust_flags = get_rust_test_flags(ctx.rule.attr)
+
+ args, env = construct_arguments(
+ ctx = ctx,
+ attr = ctx.rule.attr,
+ file = ctx.file,
+ toolchain = toolchain,
+ tool_path = toolchain.rustc.path,
+ cc_toolchain = cc_toolchain,
+ feature_configuration = feature_configuration,
+ crate_info = crate_info,
+ dep_info = dep_info,
+ linkstamp_outs = linkstamp_outs,
+ ambiguous_libs = ambiguous_libs,
+ output_hash = determine_output_hash(crate_info.root, ctx.label),
+ rust_flags = rust_flags,
+ out_dir = out_dir,
+ build_env_files = build_env_files,
+ build_flags_files = build_flags_files,
+ emit = ["dep-info", "metadata"],
+ skip_expanding_rustc_env = True,
+ )
+
+ args.process_wrapper_flags.add("--stdout-file", unpretty_out)
+
+ # Expand all macros and dump the source to stdout.
+ # Tracking issue: https://github.com/rust-lang/rust/issues/43364
+ args.rustc_flags.add("-Zunpretty={}".format(mode))
+
+ ctx.actions.run(
+ executable = ctx.executable._process_wrapper,
+ inputs = compile_inputs,
+ outputs = [unpretty_out],
+ env = env,
+ arguments = args.all,
+ mnemonic = mnemonic,
+ toolchain = "@rules_rust//rust:toolchain_type",
+ )
+
+ output_groups.update({"rust_unpretty": depset(outputs)})
+
+ return [
+ OutputGroupInfo(**output_groups),
+ ]
+
+# Example: Expand all rust targets in the codebase.
+# bazel build --aspects=@rules_rust//rust:defs.bzl%rust_unpretty_aspect \
+# --output_groups=expanded \
+# //...
+rust_unpretty_aspect = aspect(
+ implementation = _rust_unpretty_aspect_impl,
+ fragments = ["cpp"],
+ host_fragments = ["cpp"],
+ attrs = {
+ "_unpretty_modes": attr.label(
+ doc = "The values to pass to `--unpretty`",
+ providers = [RustUnprettyInfo],
+ default = Label("//rust/settings:unpretty"),
+ ),
+ } | RUSTC_ATTRS,
+ toolchains = [
+ str(Label("//rust:toolchain_type")),
+ "@bazel_tools//tools/cpp:toolchain_type",
+ ],
+ required_providers = [rust_common.crate_info],
+ doc = """\
+Executes Rust expand on specified targets.
+
+This aspect applies to existing rust_library, rust_test, and rust_binary rules.
+
+As an example, if the following is defined in `examples/hello_lib/BUILD.bazel`:
+
+```python
+load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
+
+rust_library(
+ name = "hello_lib",
+ srcs = ["src/lib.rs"],
+)
+
+rust_test(
+ name = "greeting_test",
+ srcs = ["tests/greeting.rs"],
+ deps = [":hello_lib"],
+)
+```
+
+Then the targets can be expanded with the following command:
+
+```output
+$ bazel build --aspects=@rules_rust//rust:defs.bzl%rust_unpretty_aspect \
+ --output_groups=rust_unpretty_expanded //hello_lib:all
+```
+""",
+)
+
+def _rust_unpretty_rule_impl(ctx):
+ mode = ctx.attr.mode
+ output_group = "rust_unpretty_{}".format(mode.replace(",", "_").replace("-", "_"))
+ files = []
+ for target in ctx.attr.deps:
+ files.append(getattr(target[OutputGroupInfo], output_group))
+
+ return [DefaultInfo(files = depset(transitive = files))]
+
+rust_unpretty = rule(
+ implementation = _rust_unpretty_rule_impl,
+ cfg = nightly_unpretty_transition,
+ attrs = {
+ "deps": attr.label_list(
+ doc = "Rust targets to run unpretty on.",
+ providers = [rust_common.crate_info],
+ aspects = [rust_unpretty_aspect],
+ ),
+ "mode": attr.string(
+ doc = "The value to pass to `--unpretty`",
+ values = _UNPRETTY_MODES,
+ default = "expanded",
+ ),
+ "_allowlist_function_transition": attr.label(
+ default = Label("//tools/allowlists/function_transition_allowlist"),
+ ),
+ "_unpretty_modes": attr.label(
+ doc = "The values to pass to `--unpretty`",
+ providers = [RustUnprettyInfo],
+ default = Label("//rust/settings:unpretty"),
+ ),
+ },
+ doc = """\
+Executes rust unpretty on a specific target.
+
+Similar to `rust_unpretty_aspect`, but allows specifying a list of dependencies \
+within the build system.
+
+For example, given the following example targets:
+
+```python
+load("@rules_rust//rust:defs.bzl", "rust_library", "rust_test")
+
+rust_library(
+ name = "hello_lib",
+ srcs = ["src/lib.rs"],
+)
+
+rust_test(
+ name = "greeting_test",
+ srcs = ["tests/greeting.rs"],
+ deps = [":hello_lib"],
+)
+```
+
+Rust expand can be set as a build target with the following:
+
+```python
+load("@rules_rust//rust:defs.bzl", "rust_unpretty")
+
+rust_unpretty(
+ name = "hello_library_expand",
+ testonly = True,
+ deps = [
+ ":hello_lib",
+ ":greeting_test",
+ ],
+ mode = "expand",
+)
+```
+""",
+)
diff --git a/rust/settings/BUILD.bazel b/rust/settings/BUILD.bazel
index f1ba202..1f23709 100644
--- a/rust/settings/BUILD.bazel
+++ b/rust/settings/BUILD.bazel
@@ -1,5 +1,6 @@
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
load("@bazel_skylib//rules:common_settings.bzl", "bool_flag", "string_flag")
+load("//rust/private:unpretty.bzl", "rust_unpretty_flag")
load(":incompatible.bzl", "incompatible_flag")
package(default_visibility = ["//visibility:public"])
@@ -9,6 +10,25 @@
srcs = glob(["**/*.bzl"]),
)
+rust_unpretty_flag(
+ name = "unpretty",
+ build_setting_default = [
+ "ast-tree,expanded",
+ "ast-tree",
+ "expanded,hygiene",
+ "expanded,identified",
+ "expanded",
+ "hir-tree",
+ "hir,identified",
+ "hir,typed",
+ "hir",
+ "identified",
+ "mir-cfg",
+ "mir",
+ "normal",
+ ],
+)
+
# A flag controlling whether to rename first-party crates such that their names
# encode the Bazel package and target name, instead of just the target name.
#
diff --git a/test/unit/force_all_deps_direct/force_all_deps_direct_test.bzl b/test/unit/force_all_deps_direct/force_all_deps_direct_test.bzl
index 6cb5147..39e705a 100644
--- a/test/unit/force_all_deps_direct/force_all_deps_direct_test.bzl
+++ b/test/unit/force_all_deps_direct/force_all_deps_direct_test.bzl
@@ -36,7 +36,10 @@
generator(
name = "generate",
deps = [":direct"],
- tags = ["noclippy"],
+ tags = [
+ "no-clippy",
+ "no-unpretty",
+ ],
)
force_all_deps_direct_test(
diff --git a/test/unpretty/BUILD.bazel b/test/unpretty/BUILD.bazel
new file mode 100644
index 0000000..3f0f773
--- /dev/null
+++ b/test/unpretty/BUILD.bazel
@@ -0,0 +1,49 @@
+load("@bazel_skylib//rules:diff_test.bzl", "diff_test")
+load("//rust:defs.bzl", "rust_proc_macro", "rust_test", "rust_unpretty")
+
+rust_proc_macro(
+ name = "proc_macro",
+ srcs = ["proc_macro.rs"],
+ edition = "2021",
+ visibility = ["//test:__subpackages__"],
+)
+
+rust_unpretty(
+ name = "proc_macro_unpretty",
+ testonly = True,
+ mode = "expanded",
+ deps = [":proc_macro"],
+)
+
+diff_test(
+ name = "proc_macro_unpretty_diff_test",
+ file1 = "proc_macro.unpretty.expanded.rs",
+ file2 = ":proc_macro_unpretty",
+)
+
+rust_test(
+ name = "proc_macro_test",
+ srcs = ["proc_macro_test.rs"],
+ edition = "2021",
+ proc_macro_deps = [":proc_macro"],
+)
+
+rust_unpretty(
+ name = "proc_macro_test_unpretty",
+ testonly = True,
+ mode = "expanded",
+ deps = [":proc_macro_test"],
+)
+
+rust_unpretty(
+ name = "proc_macro_test_unpretty_extra",
+ testonly = True,
+ mode = "normal",
+ deps = [":proc_macro_test"],
+)
+
+diff_test(
+ name = "proc_macro_test_unpretty_diff_test",
+ file1 = "proc_macro.unpretty.expanded.rs",
+ file2 = ":proc_macro_unpretty",
+)
diff --git a/test/unpretty/proc_macro.rs b/test/unpretty/proc_macro.rs
new file mode 100644
index 0000000..9005e0d
--- /dev/null
+++ b/test/unpretty/proc_macro.rs
@@ -0,0 +1,9 @@
+// This differs from the edition 2015 version because it does not have an `extern proc_macro`
+// statement, which became optional in edition 2018.
+
+use proc_macro::TokenStream;
+
+#[proc_macro]
+pub fn make_answer(_item: TokenStream) -> TokenStream {
+ "fn answer() -> u32 { 42 }".parse().unwrap()
+}
diff --git a/test/unpretty/proc_macro.unpretty.expanded.rs b/test/unpretty/proc_macro.unpretty.expanded.rs
new file mode 100644
index 0000000..f167085
--- /dev/null
+++ b/test/unpretty/proc_macro.unpretty.expanded.rs
@@ -0,0 +1,24 @@
+#![feature(prelude_import)]
+#[prelude_import]
+use std::prelude::rust_2021::*;
+#[macro_use]
+extern crate std;
+// This differs from the edition 2015 version because it does not have an `extern proc_macro`
+// statement, which became optional in edition 2018.
+
+use proc_macro::TokenStream;
+
+#[proc_macro]
+pub fn make_answer(_item: TokenStream) -> TokenStream {
+ "fn answer() -> u32 { 42 }".parse().unwrap()
+}
+const _: () =
+ {
+ extern crate proc_macro;
+ #[rustc_proc_macro_decls]
+ #[used]
+ #[allow(deprecated)]
+ static _DECLS: &[proc_macro::bridge::client::ProcMacro] =
+ &[proc_macro::bridge::client::ProcMacro::bang("make_answer",
+ make_answer)];
+ };
diff --git a/test/unpretty/proc_macro_test.rs b/test/unpretty/proc_macro_test.rs
new file mode 100644
index 0000000..c5f92f0
--- /dev/null
+++ b/test/unpretty/proc_macro_test.rs
@@ -0,0 +1,8 @@
+use proc_macro::make_answer;
+
+make_answer!();
+
+#[test]
+fn test_answer_macro() {
+ println!("{}", answer());
+}
diff --git a/test/unpretty/proc_macro_test.unpretty.expanded.rs b/test/unpretty/proc_macro_test.unpretty.expanded.rs
new file mode 100644
index 0000000..7261e99
--- /dev/null
+++ b/test/unpretty/proc_macro_test.unpretty.expanded.rs
@@ -0,0 +1,9 @@
+#![feature(prelude_import)]
+#[prelude_import]
+use std::prelude::rust_2021::*;
+#[macro_use]
+extern crate std;
+use proc_macro::make_answer;
+
+fn answer() -> u32 { 42 }
+