work/0000775000000000000000000000000014763657303006746 5ustar work/.gitignore0000664000000000000000000000010114763657303010726 0ustar *~ /Cargo.lock /target /doc/*.md.new /book/book /linklint.errors work/.gitlab-ci.yml0000664000000000000000000001731214763657303011406 0ustar stages: - test - comprehensive - deploy-book default: before_script: # Print version info for debugging - rustc --version ||true - cargo --version ||true # See tests/stderr.rs - export STDERRTEST_CARGO_OPTIONS=--locked image: "rust:bookworm" # Running tests without --workspace is useful because that way cargo # won't add features to our dependencies that we didn't actually enable # *in the derive-deftly or derive-deftly-macros crates*. cargo-check-deps: stage: test script: - cargo check --locked --all-features cargo-check: stage: test script: - cargo check --locked --workspace --all-features cargo-check-minfeatures: stage: test script: - cargo check --locked --no-default-features --features=minimal-1 cargo-fmt: stage: test script: - rustup component add rustfmt - maint/rustfmt --check maint-check-bizarre: stage: test script: - maint/update-bizarre --check maint-check-test-deps: stage: test script: - apt-get -y update - apt-get -y install libtoml-perl git - maint/check-test-deps # Check for FIXMEs. This is in "comprehensive" so you can sabotage # the CI, but still get it all to run - useful for iterating through CI. maint-check-todos: stage: comprehensive dependencies: [] script: - apt-get -y update - apt-get -y install libtoml-perl git - maint/check-blocking-todos cargo-clippy: stage: test image: "rust:1.84.0" script: - rustup component add clippy - cargo clippy --locked --workspace --all-features -- # Warning-only checks check-doc-tocs-xrefs-navbars: stage: test allow_failure: true script: - apt-get -y update - apt-get -y install libjson-perl libtoml-perl - maint/update-tocs --check - maint/update-reference-xrefs --check - maint/update-docs-navbars --check # Warning-only checks check-keywords-documented: stage: test allow_failure: true script: - apt-get -y update - apt-get -y install libjson-perl - maint/check-keywords-documented >&2 # Actual tests # # We pin to a particular cargo-expand: # https://github.com/dtolnay/cargo-expand/issues/179 cargo-test: stage: test # See "Using the proper Nightly Rust" in tests/tests.rs image: rustlang/rust@sha256:b3ddf3e263f50345cd3693a93f59f0ba2cc5575a430553b71db48487a5f541dc # ^ this is from tags.2025-02-10T14:08+00:00.gz (see HACKING.md) script: - ./maint/via-cargo-install-in-ci cargo-expand --version=1.0.100 --features=prettyplease - maint/cargo-test-locked --workspace --all-features -- --skip directly::check_examples cache: when: 'always' paths: - cache/* # Warning only, since this approach may be fragile and also the # testing machinery (particularly the thing that parses reference.md # is rather baroque and ad-hco). test-doc-examples: stage: comprehensive image: rustlang/rust@sha256:b3ddf3e263f50345cd3693a93f59f0ba2cc5575a430553b71db48487a5f541dc allow_failure: true script: - maint/cargo-test-locked -p derive-deftly-tests --all-features -- --nocapture directly::check_examples # Test every commit # # This may not work properly if we change the Nightly image version, # or the cargo expand version. Workaround: I believe doing that as # the only commit in an MR will still work. every-commit: stage: comprehensive image: rustlang/rust@sha256:b3ddf3e263f50345cd3693a93f59f0ba2cc5575a430553b71db48487a5f541dc script: - apt-get -y update - apt-get -y install libtoml-perl git - ./maint/via-cargo-install-in-ci cargo-expand --version=1.0.67 --features=prettyplease - maint/for-every-commit maint/cargo-test-locked --workspace --all-features -- --skip directly::check_examples cache: when: 'always' paths: - cache/* python3-mypy: stage: test image: debian:bookworm-slim script: - apt-get update && apt-get install -y mypy python3-toml - mypy --strict maint/build-docs-local - mypy --strict maint/feature-matrix-test cargo-test-minfeatures: stage: comprehensive image: rustlang/rust@sha256:b3ddf3e263f50345cd3693a93f59f0ba2cc5575a430553b71db48487a5f541dc script: - maint/cargo-test-locked -p derive-deftly-tests --no-default-features --features=derive-deftly-tests/recent,derive-deftly-tests/ui -- --skip directly::check_examples # Test on Stable Rust # # We don't enable the ui and macrotest fatures in derive-deftly-tests, since # we think their output might reasonably vary (in detail) when the with # compiler changes. But we do enable the "recent" feature to test even new # features. stable: stage: comprehensive script: - maint/cargo-test-locked --workspace --features=derive-deftly-tests/recent,derive-deftly-tests/full stable-minfeatures: stage: comprehensive script: - maint/cargo-test-locked -p derive-deftly-tests --no-default-features --features=derive-deftly-tests/recent # Test that users who like clippy don't get spurious warnings. # (eg, that the #27 lint avoidance technique hasn't regressed). stable-user-clippy: stage: comprehensive script: - rustup component add clippy - cargo clippy --locked -p pub-a -p pub-b --no-deps --all-features -- -Dwarnings # Check that the docs build cargo-doc: stage: comprehensive script: - cargo doc --locked --workspace --features=full # Check that the complete docs build without any warnings cargo-doc-all: # We turn warnings into errors; pin to avoid getting new ones willy-nilly image: "rust:1.84.0" stage: comprehensive script: - apt-get update - apt-get -y install jq - maint/fudge-for-docs-test-build - cargo doc --locked --workspace --features=full --document-private-items --message-format=json >rustdoc.json - jq --raw-output '. | select(.message.level) | .message.rendered' &2 2>&1 1>&3 3>&- | tee dprint-output - grep -m1 'define_derive_deftly! input start' dprint-output pages: rules: - if: $CI_COMMIT_BRANCH == "main" stage: deploy-book script: - ./maint/via-cargo-install-in-ci mdbook - ./maint/build-mdbook - mkdir -p public/latest - mv book/book/html public/latest/guide artifacts: paths: - public work/CHANGELOG.md0000664000000000000000000005550314763657303010567 0ustar # Changelog, MSRV policy and cargo features
## cargo features
Features are provided to allow for a build with reduced dependencies. * `full`: Metafeature. Enable all reasonable, non-experimental, features. * `full-msrv-1.56`: Metafeature. Enable all reasonable, non-experimental, features compatible with Rust 1.56. * `case`: [Case conversions](../doc_reference/index.html#case-changing), using `heck`. * `expect`: the [`expect`](../doc_reference/index.html#expect-items-expect-expr--syntax-check-the-expansion) expansion option for syntax checking. * `meta-as-expr`, `meta-as-items`: handling attributes containing (syntax-checked) expressions and items via [`${Xmeta(...) as expr / items}`](../doc_reference/index.html#tmeta-vmeta-fmeta--deftly-attributes) * `minimal-1`: Minimal feature set. Must be enabled. * `beta`: Enable [beta features](#t:beta). All of the above features except `beta` are enabled by default. ## MSRV and MSRV policy The Minimum Supported Rust Version for derive-deftly is 1.56. We expect to increase it cautiously and only with good reason. (However, MSRV increase would be a minor version bump.) Future cargo features might imply a higher MSRV. Such features might be added to the metafeature `full`, again, in a minor version bump. ## Reporting issues We can't wait to hear from you! In order to submit a bug to [the derive-deftly issue tracker][tracker] you will need an account on . You can request an account via [anonticket](https://anonticket.torproject.org/), or via the [account request form]. [tracker]: https://gitlab.torproject.org/Diziet/rust-derive-deftly/-/issues [account request form]: https://anonticket.torproject.org/user/gitlab-account/create/ ## Changelog ### 1.0.1 #### Improved * Trivial fixes to copyright notice in LICENCE. ### 1.0.0 No breaking changes since 0.14.x. #### Improved * **Declare derive-deftly 1.x, and therefore stable.** * Minor docs tidying. * CI: Routine updates for pinned test dependencies. * Improvements to internal HACKING.md. ### 0.14.6 #### Improved * docs: Fixed some version numbers in version-specific xref links. ### 0.14.5 #### Improved * Improvements to documentation cross-references and linking. * Relaxed upper dependency bounds for `strum`. ### 0.14.4 #### Fixed * Removed ill-nested divs from README.md. ### 0.14.3 #### Added * `beta` cargo feature, for unstable derive-deftly features. * BETA: `${Xmeta ... default ...}`. #### Improved * docs: Improvements to the Guide. * docs: Other documentation improvements. * docs: New experimental navbars for traversing derive-deftly docs. * Relaxed upper dependency bounds for `educe`, `itertools`. * Internal and testing improvements. ### 0.14.2 #### Improved * Improvements to the Guide. * New "Reporting issues" docs, currently in CHANGELOG.md. ### 0.14.1 #### Fixed * Failing `expect expr` now successfully points to the error in a copy of the template expansion, as intended. #### Improved * Reference now has indices of expansion and condition keywords. * Substantial improvements and additions to the Guide. * Inproved `dbg_all_keywords`; now includes user defined expansions. * Various new `
` anchors in the Reference and this changelog. * Many internal and testing improvements. ### 0.14.0 #### Breaking * Desupport syntax deprecated in 0.12.x: - Rqquire `define_derive_deftly! { Template: ... }`, no longer tolerating `define_derive_deftly! { Template = ... }`. - Require `export` to export templates (and drivers), no longer tolerating `pub`. Eg `define_derive_deftly! { export Template: ... }`. - Remove `pub_template_semver_check!` macro, in favour of `template_export_semver_check!`. #### Improved * README now hopes that we've had the last breaking changes before 1.0. * Old syntax is no longer found in various places in the documentation. #### template export semver * Versions before 0.12.1 are rejected by `template_export_semver_check!`: Compatibility with them is no longer testable in CI, and may break. (You may be able to keep using older versions, by removing the call to `template_export_semver_check!`, but if so you're on your own.) Crates that export templates, and haven't bumped their own semver since before derive-deftly 0.12.1, should probably bump semver now. ### 0.13.1 #### Fixed * Fixed MSRV violation introduced in 0.13.0. (cargo fails with "error: unsupported output in build script".) See [#103](https://gitlab.torproject.org/Diziet/rust-derive-deftly/-/issues/103). #### Improved * Declare `rust-version` in `derive-deftly-macros`'s `Cargo.toml`, too. ### 0.13.0 #### Breaking * `${approx_equal }` compares literals (except floats) by value. It can now fail (giving an error) if it can't do the comparison. * `${when }`'s must now come before anything else in their repetition, and this is checked when parsing so enforced even if not expanded. * `${Xmeta as expr}` is now behind a new `meta-as-expr` feature, which is enabled by default but not part of `minimal-1`. #### Added * New `full-msrv-1.56` feature. `full` may increase MSRV more quickly. * Provide a [stability policy](#t:stability). #### Fixed * Pasting now works correctly when some of the ingredients are raw identifiers. #### Improved * Compatibility with Rust's new [cfg checking scheme](https://blog.rust-lang.org/2024/05/06/check-cfg.html). * Improvements and fixes to reference documentation. * Many improvements to internals and tests. ### 0.12.1 #### Deprecated * Deprecate `pub` (in template definitions and driver adhoc exports) in favour of `export`; and deprecate `pub_template_semver_check!` in favour of `template_export_semver_check!`. `pub` etc. are still supported for now, but will be removed soon. ### 0.12.0 #### Breaking * Forbid defining templates named starting with lowercase letters. (This is now reserved for future expansion.) #### Deprecated * `define_derive_deftly!` calls should use `:` now, rather tha `=`. `=` is still supported, for now, but it will be removed soon. #### Added * Accept ":" in `define_derive_deftly!`, as well as "=", * Reference has stable anchors (suitable for use with `#...` in URLs) for each keyword and section. #### Improved * Guide (previously the Tutorial aka Introduction) is now an mdbook book. (Links from out-of-tree may need updating.) * Many improvements to the Guide. ### 0.11.0 #### Breaking * `${Xmeta as ...}` no longer has a default. Change to `as expr`, `as ty`, etc. * `${Xmeta as tokens}` is now `as token_stream` (and we now document its inherent precedence hazards). * `${Xmeta as ty}` and `$ftype` insert `::` before generic arguments (ie, they normalise the turbofish); this is generally breaking only with `${approx_equal ..}`. * `None`-delimited groups are ignored by `${approx_equal ..}`. * Meta attributes `#[deftly]` must actually be used in an expanded `$tmeta`/`$vmeta`/`$fmeta` expansion, or (as applicable), tested with a `tmeta`/`vmeta`/`fmeta` condition. I.e. meta attribute use checking is dynamic: merely mentioning the meta somewhere in an unused part of the template is not enough. #### Added * `${Xmeta as expr/ident/path/items}` * `meta-as-items` cargo feature (for `${Xmeta as items}`), part of `full` and enabled by default. * Parenthesised type path attributes, processed by `${Xmeta as ty}`, can now be pasted with `${paste }`. #### Improved * `${Xmeta as ty}` uses a `None`-delimited `Group` (although, this is not effective with current compilers: [rust-lang/rust#67062](https://github.com/rust-lang/rust/issues/67062). * Precedence considerations with `${define }` and `${Xmeta }` are documented in the reference. * Short-circuiting behaviour of `any` and `all` conditions is documented. * Improved some error messages. #### template export semver Updating to derive-deftly 0.11.0 is a breaking change to the API of a crate which exports templates: The scheme for collating information about meta attributes, across multiple template invocations, has been completely changed. So the calling convention for the template macro is quite incompatible. ### 0.10.5 #### Breaking (minor) * Fixed `$vdefbody` to always include precisely a comma after enum variants. The previous wrong behaviour was shown in the example output, but wasn't usable in the documented/intended manner. \#63, !325. #### Improved * Improved some error messages. * Docs clarifications and fixes. * Testing fixes and robustness improvements. ### 0.10.4 #### Added * `${ignore }` keyword. #### Fixed * Avoid spurious "unexpected token" errors after other errors. #### Improved * Documentation updates. * Slightly improved an error message. ### 0.10.3 #### Fixed * Fix `approx_equal` not to treat its arguments as equal if one of them was a prefix of the other. (#52) #### Breaking (minor) * Reject `expect expr` early, in `define_derive_deftly!`. (Such a template could never be successfully used.) #### Added * `tgens` condition: true if there are any generics. * `${dbg ..}` debug dump expansion, and `dbg(...)` debug dump condition. * `is_empty` condition for testing an expansion for emptiness. #### Improved * Improvements to reference docs. ### 0.10.2 #### Fixed * Docs references to `pub_template_semver_check` corrected (including in changelog entry for 0.10.0). #### Improved * Use the 2021 edition. Some minimum dependency requirements tightened, but: no MSRV impact. ### 0.10.1 #### Added * `${error "message"}` keyword for explicitly causing compile errors. #### Improved * Prettier output from `${dbg_all_keywords}`. * Better error messages for unecognised `#[deftly(...)]` attributes. ### 0.10.0 #### Breaking * Reject invalid templates in `define_derive_deftly!`, even if they are never invoked. * Reject `#[deftly(...)]` attributes not recognised by any relevant template. #### Added * `pub_template_semver_check!` for helping ensure semver compliance when exporting macros, and associated docs. #### Fixed * Fixed a largely-theoretical concurrency bug which could cause wrong error output if multiple invocations generated identical, but syntactically-invalid, expansions. (!262 / !263). * Improve spans in certain error messages relating to `Xmeta`. * Fix heading formatting issues in the entry for 0.9.0. #### template export semver Updating to derive-deftly 0.10.0 is a breaking change to the API of a crate which exports templates: Exported templates are not compatible due to renaming of the internal keyword `$_da_intern_crate` to `$_dd_intern_crate`. ### 0.9.2 #### Improved * docs: Fixed the README.md cross-reference and stability information. ### 0.9.1 #### Improved * Documentation and error message fixes, tidying up after renames. * Other minor documentation improvements. ### 0.9.0 #### Breaking * Crate renamed to `derive-deftly`, and many other renamings: * `#[derive_deftly_adhoc]` is now required for `derive_deftly_adhoc!` (ie, non-precanned templates). * To export a driver, `#[derive_deftly_adhoc(pub)]` is now needed, rather than `#[derive_adhoc(pub)]`. ```text Old name (0.8.x and earlier) New name (0.9.x and later) define_derive_adhoc! define_derive_deftly! #[derive(Adhoc)] #[derive(Deftly)] #[derive_adhoc(Template)] #[derive_deftly(Template)] #[adhoc(...)] #[deftly(...)] derive_adhoc_driver! derive_deftly_driver! derive_adhoc_template! derive_deftly_template! derive_adhoc_expand! derive_deftly_engine! derive_adhoc! derive_deftly_adhoc! ``` ##### template export semver Updating to `derive-deftly` from `derive-adhoc` is a breaking change to the API of a crate which exports templates. ### 0.8.1 #### Fixed * Fix doubled dollar deescaping in precanned d-a macros. Fixes `macro_rules! .. { { $$m:expr .. } ... }`. #### Improved * Improvements to macro rustdoc documentation. * Minor internal improvements. ### 0.8.0 #### Nominally breaking (template export semver) **We recommend you do *not* treat these changes as semver-breaking for template-defining crates.** * Reject misplaced `#[derive_adhoc]` attributes. * Reject malformed attributes like `#[adhoc("42")]` and `#[adhoc(std::cell::)]`. Technically these are breaking changes, since users of exported templates might have provided these wrong attributes, which were previously ignored and are now properly rejected. The attributes are interpreted by the version of derive-adhoc which defines the template. However, the likelihood of this breaking a working build seems low. #### MSRV * MSRV increased from 1.54 to 1.56. Principally because we need this to update to syn 2. 1.56 is the first release with Rust 2021, but we're not updating to the 2021 edition yet. #### Fixed * Fixed a spurious clippy lint `crate_in_macro_def` (#27). * More reasonable handling of `#[adhoc(..)]` attributes whose meta information has paths some of which are nested sub-paths of others. * Work around a strange bug with literals and spans which can cause a `compiler/fallback mismatch` panic during error reporting with some compiler versions. (!182) #### Improved * Docuemntation improvements (notably, many cross-references added from the tutorial to the reference). * Dependency updates (notably, we're now using syn 2). * Improvements to tests, CI, `HACKING.md`, Cargo lockfile handling. * Lockfile (`Cargo.lock`) is now committed to git under that name. ### 0.7.3 #### Added * `approx_equal` condition, for testing a form of token stream equivalence. #### Fixed * docs: Corrected some internal links to refer to the correct anchors. #### Improved * `$ttype`, `$tdeftype` and `$vtype` no longer include `::<>` or `<>` unless the original toplevel driver definition did. ### 0.7.2 #### Added * `${Xmeta(...)}` within pasting now defaults to `... as str`, so that doesn't need to be specified each time. ### 0.7.1 #### Fixed * Fix reference documentation examples for `$Xattr`. ### 0.7.0 #### Breaking * Reject inner attributes (`#![...]` and `//!...`) anywhere in templates: these are now reserve for future expansion. To resolve: use inner attributes instead. #### Added * New `${define }` and `${defcond }` facility for reuseable template fragments and conditions, to save repetition in templates. (#14) * Abbreviated `$<...>` syntax for `${paste ...}`. (#23) #### Fixed * Allow nested pasting when intermediate pastes aren't valid idents. * Fixed bugs relating to pasting of keywords and raw identifiers. * docs: Fixed minor bugs in the reference. ### 0.6.1 #### Fixed * Fixed a few broken docs links and similar infelicities. #### Improved * Change dollar-escaping pseudo-keyword to `$orig_dollar`. (Internal change; should not have any user-visible effect.) ### 0.6.0 #### Breaking * `$fvis` and `fvis` now refer to the top-level visibility for enums. (The previous behaviour is now available from `$fdefvis`/`fdefvis`.) * `fmeta` and `vmeta` now fail when used outside a field or variant, rather than searching through the whole item. (`$fmeta` and `$vmeta` didn't search and are unchanged.) * Actually make structs and unions be treated as having one "variant". * `$tdefgens` and `$tgens` include trailing comma when nonempty, as documented and intended. #### Added * `define_derive_adhoc!` supports doc comments. * `$fdefvis`/`fdefvis` for the textual visibility of a field. #### Fixed * Reference doc example snippets: many errors corrected. * Added missing cargo dep on `syn/extra-traits`. #### Improved * Trailing comma no longer added inside generics in `$ttype` `$vtype`. * Relaxed upper dependency bounds for `strum`, `itertools`. * docs: Reference: example snippets: now tested, and many added. * docs: Introduction: now has a Table of Contents. ### 0.5.0 #### Breaking * Case changing: non-snake-case keywords for case change instructions abolished (to make room for possible future reservation of non-snake-case keywords as user-defined ones). Change the case of the keyword to snake case. #### Improved * Documentation: tidying and a few more examples. ### 0.4.0 #### Breaking * `${paste }` no longer allows non-string literals in its content. * Invalid literal content within `${paste }` is now rejected during parsing, so even if that part of the template isn't expanded. * `${Xmeta... as lit}` is abolished, in favour of `... as str` (which has more consistent semantics). * `${Xmeta}` without `as` is no longer allowed in `${paste ...}`. * In `#[adhoc(attr="VALUE")]`, now only *string* literals are allowed. #### Added * `${paste }` and `${CASE_CHANGE ...}` can now be nested, either (or both) ways round. * `${Xmeta... as str}`, `${Xmeta... as tokens}`. #### Improved * Better error messages when `${paste }` produces a bad identifier. * docs: Minor improvements to reference. * internal: CI tests improved and extended * internal: cleanups, and internal docs improved. ### 0.3.0 #### Breaking * cargo features introduced. Currently, all enabled by default - no breakage if default features enabled. - Case conversion (and `heck` dependency) now behind `case`. - `minimal-1` introduced; it must be enabled. #### Added * Expansion options facility * `expect items`, `expect expr` option, for improved errors when template expands to invalid Rust syntax. (`expect` cargo feature.) * `for struct`, `for enum`, `for union` option, for improved errors due to misue of a template. * `dbg` option, for dumping template expansions to compiler stderr. * `$dbg_all_keywords` keyword, for dumping keyword expansions. * `full` and `minimal-1` cargo meta features. #### Improved * docs: Much expanded and improved tutorial (still a work in progress). * docs: Various corrections to reference docs. * docs: Reference documentation retitled and module renamed. * error handling: Better messages from certain situations involving multiple (incompatible) derive-adhoc versions. * tests: Made less fragile (more pinning of test dependencies). * tests: Improved CI checks on documentation, TODOs, etc. * internal: new HACKING.md file for helping develop derive-adhoc. ### 0.2.2 #### Fixed (future compatibility) * Pinned dependency from `derive-adhoc` to `derive-adhoc-macros`. * Handling of certain supposedly-future-compatible options fixed. ("future driver options" argument to `d_a_t_T`). #### Improved * Better error messages with usupported combinations of features with mixed derive-adhoc versions. #10. * Compatibility with derive-adhoc 0.2.0 tested in CI. ### 0.2.1 #### Fixed * `$vpat` expansion includes necessary post-field comma. #15. * Docs typo fixes. #### Improved * docs: Much expanded tutorial (still a work in progress) ### 0.2.0 #### Breaking * `$tgens` no longer includes defaults for generics. * `$Xattrs` by default outputs all attributes except derive-adhoc ones, rather than nothing (breaking bugfix). * `$vmeta` for a struct (not enum) processes top-level attributes, rather than imagining that there are no variant/value attributes. * Fixed hygiene (span) for `${paste }`; now it's consistently that of the template (but disagrees with the hygiene span of `$fname`). #### Added * `$fpatname` `$vpat` `$vtype`, for value matching and construction * `$fvis` `$tvis`, for visibility (also as booleans) * `is_struct` `is_union` `v_is_unit` `v_is_tuple` `v_is_named`, conditions for driver shape. * `$tdefkwd` `$tdeftype` `$tdefvariants` `$vdefbody` `$fdefine` `$tdefgens`, for defining derived types, * `$select1`, exactly-one conditional * Support exporting a template to other crates, and `$crate` expansion for referring to template crate items. * Support exporting a driver to other crates (rather hazardous). #### Fixed * Do not claim that `$ttype` includes any leading path elements. * `$` in templates always has proper span for error reporting * `$` is properly escaped in drivers * `$Xmeta` can expand to arbitrary tokens from an attribute value #### Improved * docs: New tutorial (still a work in progress) * docs: Template syntax reference overhauled * Many other minor improvements * New tests/examples ### 0.1.0 * First publicly advertised release. Much important documentation and many important features still missing. ### 0.0.1 * Initial release to crates.io, not widely advertised.
## Stability and semver policy
We take a similar approach to stability to Rust upstream, but we may make breaking changes, signaled via semver. If you write your code according to a reasonable interpretation of the documentation, and it is accepted by the compiler, we intend that it won't break with a new version of derive-deftly, unless we have declared the breakage by making a semver-breaking change to our version number. Rarely, we may need to make a change which violates that rule, especially if we discover a serious issue that might mean actual programs in the wild are likely to be not behaving as the programmer might expect. When we say that something will be "rejected", or use precisely the phrase "it is an error", we intend that you can rely on that: making it be accepted would be a breaking change. In contrast, when we write that something is "not supported", or use terminology like "must", or show it with `error,` in examples we might add support for it later. There are additional, stronger, promises relating to the API of exported templates, to help you manage the semver implications for the exporting crate. This is set out in the [relevant documentation](../macro.define_derive_deftly.html#exporting-a-template-for-use-by-other-crates).
### Beta features derive-deftly has some features which we are still experimenting with, and whose syntax and semantics might change. To use these, you must enable the [`beta`](#cargo-feature:beta) cargo feature, and, pass the [`beta_deftly`](../doc_reference/index.html#eo:beta_deftly) template option. Beta features may change their syntax or semantics or be removed, without notice. This would just be a minor version bump (i.e., increasing x in 1.x.y),. So you should pin your derive-deftly minor version: ```toml // Cargo.toml derive-deftly = { version = "~1.0.0", features = ["full", "beta"] } ``` The `beta` cargo feature may imply a higher MSRV.
work/CODE_OF_CONDUCT0000664000000000000000000000043214763657303011145 0ustar The Tor Project is committed to fostering an inclusive community where people feel safe to engage, share their points of view, and participate. For the latest version of our Code of Conduct, please see https://gitweb.torproject.org/community/policies.git/plain/code_of_conduct.txt work/Cargo.lock0000664000000000000000000004375214763657303010666 0ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "anyhow" version = "1.0.97" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f" [[package]] name = "basic-toml" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" dependencies = [ "serde", ] [[package]] name = "bizarre-derive-deftly" version = "0.0.666" dependencies = [ "bizarre-derive-deftly-macros", "heck", ] [[package]] name = "bizarre-derive-deftly-macros" version = "0.0.666" dependencies = [ "heck", "indexmap", "itertools 0.14.0", "proc-macro-crate", "proc-macro2", "quote", "sha3", "strum 0.27.1", "syn", "void", ] [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "cpufeatures" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "derive-deftly" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "263064b73d7ae758bc9d355d167546ffc6b57e4416733601d1383d097d166b14" dependencies = [ "derive-deftly-macros 0.12.1", "heck", ] [[package]] name = "derive-deftly" version = "1.0.1" dependencies = [ "derive-deftly-macros 1.0.1", "heck", ] [[package]] name = "derive-deftly-book-tests" version = "0.1.0" dependencies = [ "anyhow", "derive-deftly 1.0.1", "regex", ] [[package]] name = "derive-deftly-macros" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d15b810e497b2b54ef49ef77f944b86965cd2fa8e057ba38e54e5dc2c66ce9a" dependencies = [ "heck", "indexmap", "itertools 0.12.1", "proc-macro-crate", "proc-macro2", "quote", "sha3", "strum 0.26.3", "syn", "void", ] [[package]] name = "derive-deftly-macros" version = "1.0.1" dependencies = [ "heck", "indexmap", "itertools 0.14.0", "proc-macro-crate", "proc-macro2", "quote", "sha3", "strum 0.27.1", "syn", "void", ] [[package]] name = "derive-deftly-stderr-test" version = "0.0.1" dependencies = [ "derive-deftly 1.0.1", ] [[package]] name = "derive-deftly-tests" version = "0.0.1" dependencies = [ "derive-deftly 1.0.1", "easy-ext", "educe", "glob", "heck", "indexmap", "itertools 0.14.0", "macrotest", "paste", "proc-macro-crate", "proc-macro2", "quote", "regex", "sha3", "static_assertions", "strum 0.27.1", "syn", "toml", "trybuild", "void", ] [[package]] name = "diff" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8" [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", ] [[package]] name = "easy-ext" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc5d6d6a8504f8caedd7de14576464383900cd3840b7033a7a3dce5ac00121ca" [[package]] name = "edrv-new-b" version = "0.0.1" dependencies = [ "bizarre-derive-deftly", "edrv-old-a", ] [[package]] name = "edrv-old-a" version = "0.0.1" dependencies = [ "derive-deftly 0.12.1", ] [[package]] name = "edrv-old-b" version = "0.0.1" dependencies = [ "derive-deftly 0.12.1", "pub-a", ] [[package]] name = "educe" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" dependencies = [ "enum-ordinalize", "proc-macro2", "quote", "syn", ] [[package]] name = "either" version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7914353092ddf589ad78f25c5c1c21b7f80b0ff8621e7c814c3485b5306da9d" [[package]] name = "enum-ordinalize" version = "4.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fea0dcfa4e54eeb516fe454635a95753ddd39acda650ce703031c6973e315dd5" dependencies = [ "enum-ordinalize-derive", ] [[package]] name = "enum-ordinalize-derive" version = "4.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d28318a75d4aead5c4db25382e8ef717932d0346600cacae6357eb5941bc5ff" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "equivalent" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "glob" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" [[package]] name = "hashbrown" version = "0.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "indexmap" version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "itertools" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" dependencies = [ "either", ] [[package]] name = "itertools" version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" dependencies = [ "either", ] [[package]] name = "itoa" version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "keccak" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" dependencies = [ "cpufeatures", ] [[package]] name = "libc" version = "0.2.170" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828" [[package]] name = "macrotest" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e2035deb453578ff1cd2da2761ac78abbffffd1d06a0f59261c082ea713fdad" dependencies = [ "basic-toml", "diff", "glob", "prettyplease", "serde", "serde_derive", "serde_json", "syn", ] [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "new-b" version = "0.0.1" dependencies = [ "bizarre-derive-deftly", "old-a", ] [[package]] name = "old-a" version = "0.0.1" dependencies = [ "derive-deftly 0.12.1", ] [[package]] name = "old-b" version = "0.0.1" dependencies = [ "derive-deftly 0.12.1", "pub-a", ] [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "prettyplease" version = "0.2.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6924ced06e1f7dfe3fa48d57b9f74f55d8915f5036121bef647ef4b204895fac" dependencies = [ "proc-macro2", "syn", ] [[package]] name = "proc-macro-crate" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" dependencies = [ "toml_edit", ] [[package]] name = "proc-macro2" version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "pub-a" version = "0.0.1" dependencies = [ "derive-deftly 1.0.1", ] [[package]] name = "pub-b" version = "0.0.1" dependencies = [ "bizarre-derive-deftly", "pub-a", ] [[package]] name = "quote" version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801" dependencies = [ "proc-macro2", ] [[package]] name = "regex" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "rustversion" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4" [[package]] name = "ryu" version = "1.0.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd" [[package]] name = "serde" version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.218" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "serde_spanned" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" dependencies = [ "serde", ] [[package]] name = "sha3" version = "0.10.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" dependencies = [ "digest", "keccak", ] [[package]] name = "static_assertions" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "strum" version = "0.26.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" dependencies = [ "strum_macros 0.26.4", ] [[package]] name = "strum" version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" dependencies = [ "strum_macros 0.27.1", ] [[package]] name = "strum_macros" version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", "syn", ] [[package]] name = "strum_macros" version = "0.27.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", "syn", ] [[package]] name = "syn" version = "2.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "target-triple" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" [[package]] name = "termcolor" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" dependencies = [ "winapi-util", ] [[package]] name = "toml" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148" dependencies = [ "serde", "serde_spanned", "toml_datetime", "toml_edit", ] [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" dependencies = [ "serde", ] [[package]] name = "toml_edit" version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ "indexmap", "serde", "serde_spanned", "toml_datetime", "winnow", ] [[package]] name = "trybuild" version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b812699e0c4f813b872b373a4471717d9eb550da14b311058a4d9cf4173cbca6" dependencies = [ "glob", "serde", "serde_derive", "serde_json", "target-triple", "termcolor", "toml", ] [[package]] name = "typenum" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00e2473a93778eb0bad35909dff6a10d28e63f792f16ed15e404fca9d5eeedbe" [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "void" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e7f4ea97f6f78012141bcdb6a216b2609f0979ada50b20ca5b52dde2eac2bb1" dependencies = [ "memchr", ] work/Cargo.lock.minimal0000664000000000000000000004147714763657303012315 0ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "aho-corasick" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b1112e9e6d4a66f5f63fbaa520a0bbfe45e63cdd2d13c7ea1660ccc45345073" dependencies = [ "memchr 1.0.0", ] [[package]] name = "anyhow" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9ff2deb543832ee7b1a08060c38cc6af5816e96d3fcb6fc2e99bd15634e5c7f" [[package]] name = "autocfg" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" [[package]] name = "basic-toml" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a72cac83e42560384ff91957b3a739eba64fb0db58049f42884ceb3abb57378" dependencies = [ "serde", ] [[package]] name = "bizarre-derive-deftly" version = "0.0.666" dependencies = [ "bizarre-derive-deftly-macros", "heck", ] [[package]] name = "bizarre-derive-deftly-macros" version = "0.0.666" dependencies = [ "heck", "indexmap", "itertools", "proc-macro-crate", "proc-macro2", "quote", "sha3", "strum", "syn 2.0.53", "void", ] [[package]] name = "block-buffer" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03588e54c62ae6d763e2a80090d50353b785795361b4ff5b3bf0a5097fc31c0b" dependencies = [ "generic-array", ] [[package]] name = "crypto-common" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4600d695eb3f6ce1cd44e6e291adceb2cc3ab12f20a33777ecd0bf6eba34e06" dependencies = [ "generic-array", ] [[package]] name = "derive-deftly" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "263064b73d7ae758bc9d355d167546ffc6b57e4416733601d1383d097d166b14" dependencies = [ "derive-deftly-macros 0.12.1", "heck", ] [[package]] name = "derive-deftly" version = "1.0.1" dependencies = [ "derive-deftly-macros 1.0.1", "heck", ] [[package]] name = "derive-deftly-book-tests" version = "0.1.0" dependencies = [ "anyhow", "derive-deftly 1.0.1", "regex", ] [[package]] name = "derive-deftly-macros" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d15b810e497b2b54ef49ef77f944b86965cd2fa8e057ba38e54e5dc2c66ce9a" dependencies = [ "heck", "indexmap", "itertools", "proc-macro-crate", "proc-macro2", "quote", "sha3", "strum", "syn 2.0.53", "void", ] [[package]] name = "derive-deftly-macros" version = "1.0.1" dependencies = [ "heck", "indexmap", "itertools", "proc-macro-crate", "proc-macro2", "quote", "sha3", "strum", "syn 2.0.53", "void", ] [[package]] name = "derive-deftly-stderr-test" version = "0.0.1" dependencies = [ "derive-deftly 1.0.1", ] [[package]] name = "derive-deftly-tests" version = "0.0.1" dependencies = [ "derive-deftly 1.0.1", "easy-ext", "educe", "glob", "heck", "indexmap", "itertools", "macrotest", "paste", "proc-macro-crate", "proc-macro2", "quote", "regex", "sha3", "static_assertions", "strum", "syn 2.0.53", "toml", "trybuild", "void", ] [[package]] name = "diff" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "81a2b66e18158fd72ec8f738bd0a1d1aa099b75349b780ebf934d94b817ee77c" [[package]] name = "digest" version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cb780dce4f9a8f5c087362b3a4595936b2019e7c8b30f2c3e9a7e94e6ae9837" dependencies = [ "block-buffer", "crypto-common", ] [[package]] name = "dtoa" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5edd69c67b2f8e0911629b7e6b8a34cb3956613cd7c6e6414966dee349c2db4f" [[package]] name = "easy-ext" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed2ddb5d6d3904e83114add6bbadf2b8307b4ae9fb4b2202afde1fe7bf3b56c0" [[package]] name = "edrv-new-b" version = "0.0.1" dependencies = [ "bizarre-derive-deftly", "edrv-old-a", ] [[package]] name = "edrv-old-a" version = "0.0.1" dependencies = [ "derive-deftly 0.12.1", ] [[package]] name = "edrv-old-b" version = "0.0.1" dependencies = [ "derive-deftly 0.12.1", "pub-a", ] [[package]] name = "educe" version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "244bedfdc8964d3352120556537296ac40291ca4dc959b161b6d2c94191411d9" dependencies = [ "proc-macro2", "quote", "syn 1.0.45", ] [[package]] name = "either" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5845bf77d497f79416df39462df26d4a8b71dd6440246848ee63709476dbb9a6" [[package]] name = "generic-array" version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" dependencies = [ "typenum", "version_check", ] [[package]] name = "glob" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" [[package]] name = "hashbrown" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "362385356d610bd1e5a408ddf8d022041774b683f345a1d2cfcb4f60f8ae2db5" [[package]] name = "heck" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" [[package]] name = "indexmap" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" dependencies = [ "autocfg", "hashbrown", ] [[package]] name = "itertools" version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" dependencies = [ "either", ] [[package]] name = "itoa" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91fd9dc2c587067de817fec4ad355e3818c3d893a78cab32a0a474c7a15bb8d5" [[package]] name = "keccak" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" [[package]] name = "kernel32-sys" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5b5e7edf375e6d26243bde172f1d5ed1446f4a766fc9b7006e1fd27258243f1" dependencies = [ "winapi 0.2.4", "winapi-build", ] [[package]] name = "lazy_static" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" [[package]] name = "libc" version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a51822fc847e7a8101514d1d44e354ba2ffa7d4c194dcab48870740e327cac70" [[package]] name = "macrotest" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76ba48a0cf16430edc6822161ccc0c458c44c91e9190cfcbc3d8821894517679" dependencies = [ "basic-toml", "diff", "glob", "prettyplease", "serde", "serde_derive", "serde_json", "syn 2.0.53", ] [[package]] name = "memchr" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7492849298f0731c393b1f34ce03a7c84c00bead2e7057db9342907c8fdcae28" dependencies = [ "libc", ] [[package]] name = "memchr" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01e64d9017d18e7fc09d8e4fe0e28ff6931019e979fb8019319db7ca827f8a6" dependencies = [ "libc", ] [[package]] name = "new-b" version = "0.0.1" dependencies = [ "bizarre-derive-deftly", "old-a", ] [[package]] name = "num-traits" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51eab148f171aefad295f8cece636fc488b9b392ef544da31ea4b8ef6b9e9c39" [[package]] name = "old-a" version = "0.0.1" dependencies = [ "derive-deftly 0.12.1", ] [[package]] name = "old-b" version = "0.0.1" dependencies = [ "derive-deftly 0.12.1", "pub-a", ] [[package]] name = "paste" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ddc8e145de01d9180ac7b78b9676f95a9c2447f6a88b2c2a04702211bc5d71" [[package]] name = "prettyplease" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4508c0eff4d1e708551034de4bddd61bf8bda8c7b5ae72bd844cf68ea21117ac" dependencies = [ "proc-macro2", "syn 2.0.53", ] [[package]] name = "proc-macro-crate" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" dependencies = [ "thiserror", "toml", ] [[package]] name = "proc-macro2" version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "907a61bd0f64c2f29cd1cf1dc34d05176426a3f504a78010f08416ddb7b13708" dependencies = [ "unicode-ident", ] [[package]] name = "pub-a" version = "0.0.1" dependencies = [ "derive-deftly 1.0.1", ] [[package]] name = "pub-b" version = "0.0.1" dependencies = [ "bizarre-derive-deftly", "pub-a", ] [[package]] name = "quote" version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] [[package]] name = "regex" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75ecf88252dce580404a22444fc7d626c01815debba56a7f4f536772a5ff19d3" dependencies = [ "aho-corasick", "memchr 2.0.0", "regex-syntax", "thread_local", "utf8-ranges", ] [[package]] name = "regex-syntax" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f1ac0f60d675cc6cf13a20ec076568254472551051ad5dd050364d70671bf6b" dependencies = [ "ucd-util", ] [[package]] name = "rustversion" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c48f91977f4ef3be5358c15d131d3f663f6b4d7a112555bf3bf52ad23b6659e5" dependencies = [ "proc-macro2", "quote", "syn 1.0.45", ] [[package]] name = "serde" version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e707fbbf255b8fc8c3b99abb91e7257a622caeb20a9818cbadbeeede4e0932ff" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac5d00fc561ba2724df6758a17de23df5914f20e41cb00f94d5b7ae42fffaff8" dependencies = [ "proc-macro2", "quote", "syn 1.0.45", ] [[package]] name = "serde_json" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e9b1ec939469a124b27e208106550c38358ed4334d2b1b5b3825bc1ee37d946a" dependencies = [ "dtoa", "itoa", "num-traits", "serde", ] [[package]] name = "sha3" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31f935e31cf406e8c0e96c2815a5516181b7004ae8c5f296293221e9b1e356bd" dependencies = [ "digest", "keccak", ] [[package]] name = "static_assertions" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa13613355688665b68639b1c378a62dbedea78aff0fc59a4fa656cbbdec657" [[package]] name = "strum" version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e96acfc1b70604b8b2f1ffa4c57e59176c7dbb05d556c71ecd2f5498a1dee7f8" dependencies = [ "strum_macros", ] [[package]] name = "strum_macros" version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6878079b17446e4d3eba6192bb0a2950d5b14f0ed8424b852310e5a94345d0ef" dependencies = [ "heck", "proc-macro2", "quote", "rustversion", "syn 1.0.45", ] [[package]] name = "syn" version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea9c5432ff16d6152371f808fb5a871cd67368171b09bb21b43df8e4a47a3556" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] [[package]] name = "syn" version = "2.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7383cd0e49fff4b6b90ca5670bfd3e9d6a733b3f90c686605aa7eec8c4996032" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "termcolor" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4096add70612622289f2fdcdbd5086dc81c1e2675e6ae58d6c4f62a16c6d7f2f" dependencies = [ "wincolor", ] [[package]] name = "thiserror" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0" dependencies = [ "proc-macro2", "quote", "syn 1.0.45", ] [[package]] name = "thread-id" version = "3.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4437c97558c70d129e40629a5b385b3fb1ffac301e63941335e4d354081ec14a" dependencies = [ "kernel32-sys", "libc", ] [[package]] name = "thread_local" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7793b722f0f77ce716e7f1acf416359ca32ff24d04ffbac4269f44a4a83be05d" dependencies = [ "thread-id", "unreachable", ] [[package]] name = "toml" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a54ae44b0b2c443e7ef6dd3be16a776bae4daa40684f81e15126bc04e7747308" dependencies = [ "serde", ] [[package]] name = "trybuild" version = "1.0.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26d546652bb2140ab432f8022a8a89d661798224094756558084bac4eb34e61a" dependencies = [ "glob", "lazy_static", "serde", "serde_json", "termcolor", "toml", ] [[package]] name = "typenum" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" [[package]] name = "ucd-util" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ac9567e27ca9fc45bac22f987fd62547b0ac65d2e6502dfc09cdab7dbdba31f" [[package]] name = "unicode-ident" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" [[package]] name = "unicode-xid" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" [[package]] name = "unreachable" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba6ca90cbc7fe966c80afab0e8677f09bc157da39eb5bd54084e0d0ce2433777" dependencies = [ "void", ] [[package]] name = "utf8-ranges" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" [[package]] name = "version_check" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "45d3d553fd9413fffe7147a20171d640eda0ad4c070acd7d0c885a21bcd2e8b7" [[package]] name = "void" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9190d4fdcc6b93d290236afff590896050a971233ec853958c0e52d42bdeb72c" [[package]] name = "winapi" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5350e40d908c7e8b9e5c9edb541ca47cc617c6229d3575a46da6f550f36c96fd" [[package]] name = "winapi" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3ad91d846a4a5342c1fb7008d26124ee6cf94a3953751618577295373b32117" dependencies = [ "winapi-i686-pc-windows-gnu", "winapi-x86_64-pc-windows-gnu", ] [[package]] name = "winapi-build" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" [[package]] name = "winapi-i686-pc-windows-gnu" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a16a8e2ebfc883e2b1771c6482b1fb3c6831eab289ba391619a2d93a7356220f" [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ca29cb03c8ceaf20f8224a18a530938305e9872b1478ea24ff44b4f503a1d1d" [[package]] name = "wincolor" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9dc3aa9dcda98b5a16150c54619c1ead22e3d3a5d458778ae914be760aa981a" dependencies = [ "winapi 0.3.0", ] work/Cargo.toml0000664000000000000000000000354114763657303010701 0ustar [package] name = "derive-deftly" version = "1.0.1" edition = "2021" license="MIT" authors=["Ian Jackson ", "and the contributors to Rust derive-deftly"] description="An ergonomic way to write derive() macros" homepage = "https://gitlab.torproject.org/Diziet/rust-derive-deftly" repository = "https://gitlab.torproject.org/Diziet/rust-derive-deftly" rust-version = "1.56" autotests = false [workspace] members = [ "macros", "tests", # TODO dh-rust: should be a way to filter these out without hacking the source # "tests/pub-export/pub-a", # "tests/pub-export/pub-b", # "tests/compat/old-a", # "tests/compat/new-b", # "tests/compat/old-b", # "tests/compat/edrv-old-a", # "tests/compat/edrv-new-b", # "tests/compat/edrv-old-b", # "tests/stderr", "book/tests", ] # After editing this file, you will probably need to run # maint/update-bizarre # to update the "bizarre" testing versions in tests/pub-export/bizarre-* [features] default = ["full"] full = ["full-msrv-1.56"] "full-msrv-1.56" = [ "case", "expect", "meta-as-expr", "meta-as-items", "minimal-1", ] minimal-1 = [] # The minimal-1 feature is mandatory. # This apporach will allow us to move things from "always enabled" to # "part of some feature that can be disabled" without a semver break: # Introduce "minimal-2" which excludes the newly-disablable code, # but retain it in "minimal-1". case = ["derive-deftly-macros/case", "heck"] expect = ["derive-deftly-macros/expect"] meta-as-expr = ["derive-deftly-macros/meta-as-expr"] meta-as-items = ["derive-deftly-macros/meta-as-items"] beta = ["derive-deftly-macros/beta"] [dependencies] derive-deftly-macros = { path = "macros", version = "=1.0.1", default-features = false } # This dependency is *here* only for the link to `heck` in the docs. heck = { version = ">=0.4, <0.6", optional = true } work/DEVELOPER-CERTIFICATE0000664000000000000000000000261614763657303011743 0ustar Developer Certificate of Origin Version 1.1 Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 1 Letterman Drive Suite D4700 San Francisco, CA, 94129 Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Developer's Certificate of Origin 1.1 By making a contribution to this project, I certify that: (a) The contribution was created in whole or in part by me and I have the right to submit it under the open source license indicated in the file; or (b) The contribution is based upon previous work that, to the best of my knowledge, is covered under an appropriate open source license and I have the right under that license to submit that work with modifications, whether created in whole or in part by me, under the same open source license (unless I am permitted to submit under a different license), as indicated in the file; or (c) The contribution was provided directly to me by some other person who certified (a), (b) or (c) and I have not modified it. (d) I understand and agree that this project and the contribution are public and that a record of the contribution (including all personal information I submit with it, including my sign-off) is maintained indefinitely and may be redistributed consistent with this project or the open source license(s) involved. work/HACKING.md0000777000000000000000000000000014763657303013201 2macros/HACKING.mdustar work/LICENCE0000664000000000000000000000211314763657303007730 0ustar Copyright (C) 2022 onwards, Ian Jackson, Tor Project Inc, and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. work/README.md0000664000000000000000000000533514763657303010233 0ustar # derive-deftly: An ergonomic replacement for many proc macros `derive-deftly` allows you to write macros which are driven by Rust data structures, just like proc macro derive macros, but without having to wrestle with the proc macro system. ## Overview You can write an ad-hoc template, which can speak about the fields and types in the data structure. You can also define named templates and apply them to multiple structures: effectively, you can define your own derive macro. You **don't** need to make a separate proc macro crate, write to the `syn` and `proc_macro` APIs. take care to properly propagate compile errors, or, generally, do any of the things that make writing proc macros so complicated. The template language resembles the "expander" part of a `macro_rules` macro, but you don't have to write the "matcher" part: derive-deftly parses the input data structure for you, and makes the pieces available via predefined expansion variables. [Full documentation][navbar-versioned-url-overall-toc] is available in the `doc_` module(s), the docs for the individual proc macros, and the [Guide][user guide]. ## Simple example - providing `Vec` containing enum variant names ``` use derive_deftly::{define_derive_deftly, Deftly}; define_derive_deftly! { ListVariants: impl $ttype { fn list_variants() -> Vec<&'static str> { vec![ $( stringify!( $vname ) , ) ] } } } #[derive(Deftly)] #[derive_deftly(ListVariants)] enum Enum { UnitVariant, StructVariant { a: u8, b: u16 }, TupleVariant(u8, u16), } assert_eq!( Enum::list_variants(), ["UnitVariant", "StructVariant", "TupleVariant"], ); ``` ## Next steps Why not have a look at our friendly [user guide]? It will walk you through derive-deftly's most important features, with a number of worked examples. Alternatively, there is comprehensive [reference documentation][navbar-versioned-url-overall-toc]. [user guide]: https://diziet.pages.torproject.net/rust-derive-deftly/latest/guide/ [navbar-versioned-url-overall-toc]: https://docs.rs/derive-deftly/1.0.1/derive_deftly/index.html#overall-toc work/book/0000775000000000000000000000000014763657303007700 5ustar work/book/TODO0000664000000000000000000000166014763657303010373 0ustar Refactoring the book: - Move constructor-debugging.md to new section. It has nothing to do with the constructor example. - Transforming names and strings goes into a new example, possibly about discriminants. - Discussion of "expect items" is out-of-place and unmotivated. - "Naming conventions" is not exactly an "advanced topic". Unmentioned keywords and conditions from the reference: - $dbg_all_keywords - $Xattrs - $ignore - $tgnames - approx_eq - dbg - is_empty Mentioned but unused in examples: - all, any - $fvis - vmeta - tgens - name and string manipulation Concepts to explain: - Using the const _:()={...} trick. - Namespace management. - Using macro_rules!() - Generating rustdoc documentation for your generated things. - Debugging (more detail) - Examples to stress-test your template. - The complete syntax for expansions, conditions - The complete template syntax - Silly prelude tricks. work/book/book.toml0000664000000000000000000000131714763657303011531 0ustar [book] authors = ["Nick Mathewson"] language = "en" multilingual = false src = "src" title = "Advanced Rust macros with derive-deftly" # Link checking is done using a separate script, `maint/check-mdbook-links`, # which runs linklint over a combination of this book, and our rustdocs. [output.html] # The output directories chosen by mdbook depend on whether there # is only one `[output]`, or several (!) # This is not documented (!!) # # I discovered it here # https://github.com/Michael-F-Bryan/mdbook-linkcheck/blob/bffff60e6de6f755fab9dfd6ecc3f6cf872c633d/README.md?plain=1#L56 # # Add a dummy `[output]` entry so that if we change the number outputs, # things don't move about. [output.null] command = "wc" work/book/src/0000775000000000000000000000000014763657303010467 5ustar work/book/src/SUMMARY.md0000664000000000000000000000345414763657303012154 0ustar - [Introduction](intro.md) - [Getting started](getting-started.md) - [Before you begin](before-you-begin.md) - [A first example: derive_deftly(HelloWorld)](basic-template.md) - [Using templates from other crates and modules](modularity.md) - [Templates inside modules](templates-in-modules.md) - [Exporting templates](exporting-templates.md) - [If you're only deriving once...](adhoc.md) - [Example 1: Writing your own Clone](clone-intro.md) - [Working with structs and fields](clone-fields.md) - [Tuple and unit structs](clone-tuple-unit.md) - [Making MyClone apply to enumerations](clone-enums.md) - [Making MyClone apply to generics](clone-generics.md) - [Making MyClone apply conditionally](clone-conditionally.md) - [Some more advanced topics](advanced-intro.md) - [Transforming names and strings](paste-and-case.md) - [Naming conventions for derive-deftly expansions](expansions-naming.md) - [Example 2: Defining a constructor function](constructor-intro.md) - [Limiting a template to one kind of type](constructor-limitations.md) - [Debugging templates](constructor-debugging.md) - [Working with visibility](constructor-visibility.md) - [Using attributes to make a template take arguments](constructor-attrs.md) - [Getting started if/then/else conditionals](constructor-conditionals.md) - [Making Constructor set fields with Default](constructor-advanced-conditionals.md) - [Managing complexity with $define](constructor-define.md) - [Example 3: A type to track the differences between objects](difference-intro.md) - [Some difficulties when creating a Diff type](difference-problems.md) - [A brute-force approach for applying to structs and enums](difference-if-enum.md) - [A better solution to our Diff problems](difference-solving.md) - [Other features](other-features.md) work/book/src/adhoc.md0000664000000000000000000000376114763657303012076 0ustar # If you're only deriving once... If you want, you can [apply a template to an existing type][adhoc] without having to name that template. You might want to do this if you have a template that you only want to apply to a single struct, and so you don't want to bother naming it. Supposing that you wanted to apply the template above to `MyStruct` and `MyStruct` alone, you could have said: ```rust use derive_deftly::{Deftly, derive_deftly_adhoc}; #[derive(Clone, Debug, Deftly)] #[derive_deftly_adhoc] pub struct MyStruct; derive_deftly_adhoc!{ MyStruct: impl $ttype { pub fn print_name() { println!("The name of this type is {}", stringify!($ttype)); } } } ``` Of course, that's not so useful yet. In this case, it would have been easier just to write `impl MyStruct { pub fn print_name() { ... } }`. But soon, we'll see how to write more interesting templates, and how to use them to create much more interesting code. ## What's next In the next session, we'll start working on a `Clone` example, and we'll learn how to work with structs, enums, fields, variants, and more. (But if you're the kind of person who wants to skip straight to the reference manual, you can find it [over here][reference].) [adhoc]: https://docs.rs/derive-deftly/latest/derive_deftly/macro.derive_deftly_adhoc.html [reference]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html work/book/src/advanced-intro.md0000664000000000000000000000134514763657303013712 0ustar # Some more advanced topics Now that we've our first basic example under our belt, let's look at some other things that `derive-deftly` can do. work/book/src/basic-template.md0000664000000000000000000000725414763657303013713 0ustar # A first example: derive_deftly(HelloWorld) Here we'll make an example project with a simple `derive-deftly` template. Our template won't do much, but it will get us started using the system. To begin with, we'll create a new project: > ```shell > $ cargo init --lib dd-hello-world > $ cd dd-hello-world > ``` Next, we'll add the `derive-deftly` crate as a new dependency: ```shell $ cargo add derive-deftly ``` Now we can edit `src/lib.rs` in our project to define and use a new template. ## Writing our HelloWorld template There are two parts to using derive-deftly: specifying _templates_ that you can use to derive new features for your structs and enums and then _applying_ those templates to your types. To define a template, you use [`define_derive_deftly!`], as in ```rust use derive_deftly::define_derive_deftly; define_derive_deftly! { HelloWorld: impl $ttype { pub fn greet() { println!("Greetings from {}", stringify!($ttype)); } } } ``` This is a very simple template: it uses a single expansion: [`$ttype`]. (We'll get into more expansions, and more useful examples, later on. For now, all you need to know is that `$ttype` expands to the type on which you're applying your template.) Later on, you can apply `HelloWorld` to your own type with [`#[derive(Deftly)]`[derive-Deftly], as in: ```rust # use derive_deftly::define_derive_deftly; # define_derive_deftly! { # HelloWorld: # # impl $ttype { # pub fn greet() { # println!("Greetings from {}", stringify!($ttype)); # } # } # } use derive_deftly::Deftly; #[derive(Clone, Debug, Deftly)] #[derive_deftly(HelloWorld)] pub struct MyStruct; MyStruct::greet(); ``` ## Limitations of our template Our template won't work for every type! For example, suppose that we try to apply it to a generic type: ```rust,ignore #[derive(Deftly)] #[derive_deftly(HelloWorld)] struct Pair { first: T, second: T, } ``` In response, derive-deftly will try to expand our template to something like this: ```rust,ignore impl Pair { pub fn greet() { println!("Greetings from {}", stringify!(Pair)); } } ``` But that won't work in Rust. Instead, we would need a template to expand to something more like this: ```rust,ignore impl Pair { ... } ``` We'll talk about how to make templates work for types like this in later chapters. ## What's next? Next, we'll take a detour, and show how to expose our template so that it can be used in other modules, and other crates. After that, we'll look over a more complex example, and learn more features of `derive-deftly`. We'll write a more useful template, and have it apply to more types. [`$ttype`]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:ttype [`define_derive_deftly!`]: https://docs.rs/derive-deftly/latest/derive_deftly//macro.define_derive_deftly.html [derive-Deftly]: https://docs.rs/derive-deftly/latest/derive_deftly/derive.Deftly.html work/book/src/before-you-begin.md0000664000000000000000000000320314763657303014145 0ustar # What you should know before you start Before you start using `derive-deftly`, there are a few things t2hat this guide assumes you are already familiar with. Don't feel bad if you don't know this stuff already; just make a note to come back later once you're ready. * We assume that you are already familiar with the Rust programming language and the Cargo package management tool. If you're not, maybe try the [Rust Book] or [_Rust By Example_]. * We assume that you are comfortable writing, building, compiling, and debugging Rust programs. * We assume that you are already familiar with the `macro_rules!` syntax for defining "macros by example". If you're not, see the [relevant][book-macros] [sections][ex-macros] in the above books. If these assumptions are true for you, then let's move ahead! [Rust Book]: https://doc.rust-lang.org/book/ [_Rust By Example_]: https://doc.rust-lang.org/rust-by-example/ [book-macros]: https://doc.rust-lang.org/book/ch19-06-macros.html [ex-macros]: https://doc.rust-lang.org/rust-by-example/macros.html work/book/src/clone-conditionally.md0000664000000000000000000001164014763657303014761 0ustar # Making MyClone apply conditionally Now, for the first time, we will make `MyClone` do something that Rust's `#[derive(Clone)]` does not: it will apply only when the fields of a struct are `Clone`. For example, suppose have a struct like this: ```rust # use std::sync::Arc; struct Indirect(Arc, u16); ``` If you try to derive `Clone` on it, the compiler will generate code something like this: ```rust,ignore impl Clone for Indirect { ... } ``` But that `T: Clone` constraint isn't strictly necessary: `Arc` always implements `Clone`, so your struct could have been be `Clone` unconditionally. But using derive-deftly, you can define a template that derives `Clone` only for the cases where the _actual_ required constraints are met: ```rust # use derive_deftly::{define_derive_deftly,Deftly}; define_derive_deftly! { MyClone: impl<$tgens> Clone for $ttype where $twheres // This is the new part: $( $ftype : Clone , ) { // (The rest is as before...) fn clone(&self) -> Self { match self { $( $vpat => $vtype { $( $fname: $fpatname.clone(), ) }, ) } } } } # use std::{sync::Arc, fmt::Debug} ; # #[derive(Deftly)] # #[derive_deftly(MyClone[dbg])] # struct Example where T: Debug { # arc: Arc # } # #[derive(Deftly)] # #[derive_deftly(MyClone[dbg])] # struct Example2 { # arc: Arc # } ``` Here, we are using [`$ftype`][x:ftype]. ("field type") to get the actual type of each field. Since we're repeating it with `$( ... )`, we are requiring every field to be `Clone`. Will this work with non-generic fields, or if the same field is used more than once? Once again, yes! To Rust, this is perfectly valid: ```rust,ignore impl Clone for Direct where T: Clone, T: Clone, String: Clone { ... } ``` ## What about that comma? If you're paying close attention, you might have thought we had a syntax error above when we didn't use an explicit comma after `where $twheres`. This time, `derive_deftly` has exactly _one_ piece of cleverness at work. It makes sure that either `$twheres` is empty, or that it ends with a comma. That way, when your template expands `where $twheres $( $ftype : Clone , )` it won't produce where U: Debug + Clone T: Clone` (which is a syntax error) or `where ,` (which is also a syntax error). ##### A further note on repetition Note that when we define our additional `where` clauses above, we said `where $( $ftype: Clone, )` at the top level. We didn't have to specify separate of repetition for variants and fields: if we have only `$ftype` in a top-level repetition, `derive_deftly` will iterate over all fields in all variants. Sometimes, if you do something subtle, derive-deftly may not be able to figure out what you're trying to repeat over. You can use [`${for fields {...}}`][x:for] or [`${for variants {...}}`][x:for] to specify explicitly what you want to repeat. So, above, instead, we could have written ```rust # use derive_deftly::{Deftly, derive_deftly_adhoc}; # #[derive(Deftly)] # #[derive_deftly_adhoc] # enum TestCase { Variant(A) } # derive_deftly_adhoc! { TestCase: # fn testcase<$tgens>() where ${for fields { $ftype: Clone, }} # {} # } ``` You can also use `${for ...}` rather than `$(...)` in cases where you feel it makes your macro code clearer. For example, we could have used `${for}` to write our `MyClone` example more explicitly like this: ```rust # use derive_deftly::define_derive_deftly; define_derive_deftly! { MyClone: impl<$tgens> Clone for $ttype where $twheres ${for fields { $ftype: Clone , }} { fn clone(&self) -> Self { match self { ${for variants { $vpat => $vtype { ${for fields { $fname: $fpatname.clone(), }} }, }} } } } } ``` [t:repetition]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#t:repetition [x:for]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:for [x:ftype]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:ftype work/book/src/clone-enums.md0000664000000000000000000000776014763657303013250 0ustar # Making MyClone apply to enumerations At this point, you've probably noticed that we've defined `MyClone` to apply to `struct`s only, but it won't (yet) work on `enum`s. Let's fix that! Suppose that we have enumeration defined like this: ```rust enum AllTypes { NoData, Tuple(u8, u16), Struct { a: String, b: String } } ``` We want to make sure that MyClone can recognize and re-construct each of the three variants. We can do that as follows: ```rust # use derive_deftly::define_derive_deftly; define_derive_deftly! { MyClone: impl Clone for $ttype { fn clone(&self) -> Self { match self { $( $vpat => $vtype { $( $fname: $fpatname.clone(), ) }, ) } } } } ``` Note that now we have two levels of nested repetition. First, we match once for each variant. (This is at the `$vpat` and `$vtype` level.) Then we match once for each field of each variant. (This is at the `$fname` and `$fpatname` level.) Let's go over the new expansions here. First, we have [`$vpat`][x:vpat] ("variant pattern"): that expands to a pattern that can match and deconstruct a single variant. Then, we have [`$vtype`][x:vtype] ("variant type"): that's the type of the variant, suitable for use as a constructor. Then, inside the variant, we have `$fname`: that's our field name, which we've seen it before. Finally, we have [`$fpatname`][x:fpatname] ("field pattern name"): that is the name of the variable that we used for this field in the pattern that deconstructed it. When we apply `MyClone` to our enumeration, we get something like this: ```rust # enum AllTypes { NoData, Tuple(u8, u16), Struct { a: String, b: String } } impl Clone for AllTypes { fn clone(&self) -> Self { match self { AllTypes::NoData {} => AllTypes::NoData {}, AllTypes::Tuple { 0: f_0, 1: f_1, } => AllTypes::Tuple { 0: f_0.clone(), 1: f_1.clone() }, AllTypes::Struct { a: f_a, b: f_b, } => AllTypes::Struct { a: f_a.clone(), b: f_b.clone() }, } } } ``` Note the `f_` prefixes on the variables in our patterns, and in our `$fpatname` expansions: without them, our tuple variants would contain invalid patterns like `0: 0`. Note also that our template above will still work fine on a regular struct, even though it's written for an `enum`. If we apply the version of `MyClone` above to `struct Example { a: u8, b: String }`, we get this: ```rust # struct Example { a: u8, b: String } impl Clone for Example { fn clone(&self) -> Self { match self { Example { a: f_a, b: f_b, } => Example { a: f_a.clone(), b: f_b.clone(), } } } } ``` So (in this case at least) we were able to write a single template expansion that worked for both `struct`s and enum`s. [x:vpat]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:vpat [x:vtype]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:vtype [x:fpatname]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:fpatname work/book/src/clone-fields.md0000664000000000000000000000733514763657303013365 0ustar # Working with structs and fields Let's start by imagining that we had to write Clone from scratch for a simple structure like this: ```rust struct GiftBasket { n_apples: u8, n_oranges: u8, given_from: Option, given_to: String, } ``` We'd probably write something like this: ```rust # struct GiftBasket { # n_apples: u8, # n_oranges: u8, # given_from: Option, # given_to: String, # } impl Clone for GiftBasket { fn clone(&self) -> Self { Self { n_apples: self.n_apples.clone(), n_oranges: self.n_oranges.clone(), given_from: self.given_from.clone(), given_to: self.given_to.clone() } } } ``` (In reality, since `n_apples` and `n_oranges` both implement `Copy`, you wouldn't actually call `clone()` on them. But the compiler should be smart enough to optimize the `clone()` away.) If you imagine generalizing this to any simple struct with named fields, you might come up with a pseudocode template like this one: ```text,ignore impl Clone for ⟪Your struct⟫ { fn clone(&self) -> Self { Self { for each field: ⟪field name⟫: self.⟪field name⟫.clone(), } } } ``` And here's how that pseudocode translates into a derive-deftly template: ```rust use derive_deftly::define_derive_deftly; define_derive_deftly! { MyClone: impl Clone for $ttype { fn clone(&self) -> Self { Self { $( $fname : self.$fname.clone() , ) } } } } ``` Let's look at that template piece by piece. Certain marked "expansion keywords" in the contents (those starting with `$`) are replaced with a value that depends on the struct we are applying the template to. You've already seen `$ttype` ("top-level type"): it expands to the type on which you are applying the macro. There are two new pieces of syntax here, though: [`$( ... )`][t:repetition] and [`$fname`][x:fname]. In derive-deftly templates, `$( ... )` denotes repetition: it repeats what is inside it an "appropriate" number of times. (We'll give a definition of "appropriate" [later on][repetition].) Since we want to clone every field in our struct, we are repating the `field: self.field.clone() ,` part of our implementation once for each field. The `$fname` ("field name") expansion means "the name of the current field". Which field is that? Since `$fname` occurs inside `$( ... )`, we will repeat the body of the `$( ... )` once for each field, and expand `$fname` to the name of a different field each time. (Again, more advanced repetition is possible; there's [more to come][repetition].) ## An aside on naming At this point, you've seen expansions with names like `$ttype` and `$fname`, and you may be wondering where these names come from. If you want to know more, you can skip ahead to the section on [naming conventions](./expansions-naming.md). [t:repetition]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#t:repetition [x:fname]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:fname [repetition]: clone-conditionally.md#a-further-note-on-repetition work/book/src/clone-generics.md0000664000000000000000000000703414763657303013712 0ustar # Making MyClone apply to generics We've gotten a `MyClone` implementation that works with several kinds of struct struct, and with enums too. But here's a structure where our current `MyClone` implementation will fall flat: ```rust # use std::fmt::Debug; struct MyItems where U: Clone + Debug { things: Vec, items: Vec } ``` When we go to expand our template, it will generate something like: ```rust,ignore impl Clone for MyItems { ... } ``` That isn't valid! For our expansion to compile, we would need to use the generic parameters and their "where" constraints, like so: ```rust,ignore impl Clone for MyItems where U: Clone+Debug { ... } ``` To do this with `derive-deftly`, we have to expand our `MyClone` templatate as shown below: Therefore, to get our `MyClone` template working with generic types, we can expand it to add the same parameters and constraints. ```rust # use derive_deftly::{define_derive_deftly,Deftly}; define_derive_deftly! { MyClone: // (The next three lines are new) impl<$tgens> Clone for $ttype where $twheres { // (The rest is as before...) fn clone(&self) -> Self { match self { $( $vpat => $vtype { $( $fname: $fpatname.clone(), ) }, ) } } } } # use std::fmt::Debug; # #[derive(Deftly)] # #[derive_deftly(MyClone)] # struct MyItems # where U: Clone + Debug # { # things: Vec, # items: Vec # } # # #[derive(Deftly)] # #[derive_deftly(MyClone)] # enum TestCase { Variant(A) } ``` Here we meet two new keywords. [`$tgens`][x:tgens] ("top-level generics") becomes the generic parameters as declared on the top-level type. (In our case, that's `T:Clone, U`.) The [`$twheres`][x:twheres] keyword ("top-level where clauses") becomes the `where` constraints as declared on the top-level type. (In our case, that's `U: Clone+Debug`.) Note that `$ttype` expands to the top-level _type_: that's now `MyItems`, which is what we want in this case. If we had wanted only `MyItems` without ``, we would say [`$tname`][x:tname] instead. ## But does the syntax work for non-parameterized types? Will this template still work for non-parameterized types? Indeed, it will! To Rust, this syntax is perfectly fine: ```rust struct Simple { a: String } // Note empty <> and "where" list: impl<> Clone for Simple where { fn clone(&self) -> Self { Self { a: self.a.clone(), } } } ``` [x:tgens]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:tgens [x:twheres]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:twheres [x:tname]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:tname work/book/src/clone-intro.md0000664000000000000000000000306614763657303013247 0ustar # Example 1: Writing your own Clone In the previous chapters, we've learned how to define a simple `derive-deftly` template, and how to export it to the rest of the world. In this chapter, we'll use derive-deftly to implement our own version of Rust's standard `#[derive(Clone)]` macro. At first, it will be very simple; later on, we'll add a few features that Rust's `derive(Clone)` doesn't have. With these examples, we'll learn more feature of derive-deftly, including: * Writing templates that apply to structs and enums. * Writing templates that apply to generic types. * Using a variety of expansions, including ones for top-level types, variants, and fields. > Aside: > > We've picked a simple trait to derive on purpose, > so that we can focus on the features of derive-deftly > without the additional complexity > of introducing an unfamiliar trait as well. > > Please let us know if this approach works for you! > We're learning how to explain these concepts > as we go along. work/book/src/clone-tuple-unit.md0000664000000000000000000000336514763657303014224 0ustar # Will MyClone apply to tuple and unit structs? Rust defines several kinds of struct: * structs with fields like `struct Foo {...};` * tuple structs like `struct Foo(...);` * and unit structs like `struct Foo;` So far, you've only applied your `MyClone` template to a struct with named fields. But with a tuple struct, or a unit struct, you might expect it to fail. Surprisingly, our template still works fine! This isn't because of any clever trickery from derive-deftly: it's just how Rust works. When you use `MyClone` on tuple or unit struct, it will expand to code like this the example below. And this syntax, though not the normal way to work with these types, is still valid Rust! ```rust struct TupleStruct(String, Option); impl Clone for TupleStruct { fn clone(&self) -> Self { Self { 0: self.0.clone(), 1: self.1.clone(), } } } struct UnitStruct; impl Clone for UnitStruct { fn clone(&self) -> Self { Self { } } } ``` This will be a common theme when using derive-deftly: Rust often lets you use a slightly unidiomatic syntax, so that you can handle many different cases in the same way. work/book/src/constructor-advanced-conditionals.md0000664000000000000000000000714514763657303017634 0ustar # Making Constructor set fields with Default Sometimes, we'd like to make a template template that behave in different ways for different fields. For example, let's suppose that we want our `Constructor` template to be able to set fields to their default values, and not take them as arguments. We can do this with an explicit conditional for each field: ```rust # use derive_deftly::define_derive_deftly; define_derive_deftly! { Constructor: impl<$tgens> $ttype where $twheres { $tvis fn // This is the function name, same as before. ${if tmeta(constructor(newfn)) { ${tmeta(constructor(newfn)) as ident} } else { new } } ( // These are the function arguments: $( ${when not(fmeta(constructor(default))) } // (1) $fpatname: $ftype , ) ) -> Self { Self { $( $fname: ${if fmeta(constructor(default)) { ::std::default::Default::default() // (2) } else { $fpatname } } , ) } } } } use derive_deftly::Deftly; #[derive(Deftly)] #[derive_deftly(Constructor)] struct Foo { #[deftly(constructor(default))] s: Vec, n: u32, } ``` Here we're using a new construct: [`$when`][x:when]. It's only valid inside a loop like `$( ... )` or `${for ...}`. It causes the expansion of the loop body to be suppressed whenever the condition is not true. The condition in this cases is `not(fmeta(constructor(default)))`. (See `// (1)`.) You've seen `fmeta` before; it's true when a given attribute is present on the current field. The [`not`][c:not] condition is just how we express negation. All together, this `$when` keyword causes each field that has `#[deftly(Constructor(default))]` applied to it to be omitted from the list of arguments to the `new()` function. > Note at `// (2)` that we're using `::std::default::Default::default()`, > rather than calling `Default::default()` unconditionally. > Remember, macros expand at the position where they are invoked, > and it's possible that we'll be invoked in a module > that has been built [without the standard prelude][no_implicit_prelude]. Besides `$not`, you can use other boolean operators in conditions too: there is an [`any(...)`][c:any] that is true whenever at least one of its arguments is true, and an [`all(...)`][c:all] that is true when _all_ of its arguments are true. [c:all]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#c:all [c:any]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#c:any [c:not]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#c:not [x:when]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:when [no_implicit_prelude]: https://doc.rust-lang.org/reference/names/preludes.html#the-no_implicit_prelude-attribute work/book/src/constructor-attrs.md0000664000000000000000000000674514763657303014545 0ustar # Using attributes to make a template take arguments Let's suppose we want to make our `Constructor` template a little more flexible: we'd like to be able to give the `new` function a different name. The usual way to pass an option to a derive macro is with an attribute, something like this: ```rust,ignore #[derive(Deftly)] #[derive_deftly(Constructor)] #[deftly(constructor(newfn="function_name"))] struct Example(...); ``` We can extend our template to take a function name, like this: ```rust # use derive_deftly::define_derive_deftly; define_derive_deftly! { Constructor for struct: impl<$tgens> $ttype where $twheres { $tvis fn ${tmeta(constructor(newfn)) as ident} // (1) ( $( $fname: $ftype , ) ) -> Self { Self { $( $fname , ) } } } } use derive_deftly::Deftly; #[derive(Deftly)] #[derive_deftly(Constructor)] #[deftly(constructor(newfn="construct_example"))] struct Example { a: f64, b: String } ``` Here, instead of specifying "`new`" for the method name in our template, we give the name as `${tmeta(constructor(newfn))} as ident`. This tells the template to look for an [`#[deftly(constructor(newfn="..."))]`][deftly-attribute] attribute on the type, to interpret that attribute as an identifier, and to use the value of that attribute in place of the keyword. The `as ident` portion is mandatory; without it, derive-deftly can't tell how to parse the argument to the argument. Instead of `ident`, you can describe other kinds syntactical types, such as `str`, `ty`, `path`, or `expr`. See the [reference][x:tmeta] for a complete list. Note that we explicitly named our attribute as `constructor(newfn)`. We could instead have specified it as `newfn`, but that would have the potential to conflict with attributes interpreted by other templates. As a best practice, if we expect our template to be widely used, we should namespace our attributes as in the example above. The [`$tmeta`][x:tmeta] ("toplevel meta") keyword that we used here tells the template to look at the `#[deftly]` attributes for the _type_. We can, instead, use [`$vmeta`][x:vmeta] ("variant meta") to look for `#[deftly]` attributes for the current _variant_, or [`$fmeta`][x:fmeta] ("field meta") to to look for `#[deftly]` attributes for the current _field_. (We'll see [an example of using `$fmeta`](./constructor-advanced-conditionals.md) in a little while.) [x:tmeta]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:tmeta [x:vmeta]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:vmeta [x:fmeta]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:fmeta [deftly-attribute]: https://docs.rs/derive-deftly/latest/derive_deftly/derive.Deftly.html#deftly-attribute work/book/src/constructor-conditionals.md0000664000000000000000000001076014763657303016066 0ustar # Getting started with if/then/else conditionals In the example above, we made it possible to rename the "new" function generated by our template. But our approach is flawed: if the user _doesn't_ provide the `#[deftly(newfn)]` attribute, the expansion of `${tmeta(constructor(newfn)) ...}` will fail with an error! Let's show how to fix that: ```rust # use derive_deftly::define_derive_deftly; define_derive_deftly! { Constructor for struct: impl<$tgens> $ttype where $twheres { $tvis fn // (1) This "if" defines the function name: ${if tmeta(constructor(newfn)) { ${tmeta(constructor(newfn)) as ident} } else { new } } // The rest is as before: ( $( $fname: $ftype , ) ) -> Self { Self { $( $fname , ) } } } } # # use derive_deftly::Deftly; # #[derive(Deftly)] # #[derive_deftly(Constructor)] # #[deftly(constructor(newfn="construct_example"))] # struct Example { # a: f64, # b: String # } # #[derive(Deftly)] # #[derive_deftly(Constructor)] # struct Example2 { # a: f64, # b: String # } ``` Have a look at the part of the template marked with `// (1)`. It introduces a new concept: _conditional expansion_. The [`${if cond {expansion}}`][x:if] keyword checks whether a given _condition_ is true. If it is, then the `$if` keyword expands to the expansion second argument. Otherwise, it expands to an "else" argument (if any). > Also, you can chain `$if`s, as in > `${if COND1 { ... } else if COND2 { ... } else { ... }` > and so on! Here, the condition is [`tmeta(constructor(newfn))`][c:tmeta] ("toplevel metavalue"). That condition is true if the current type has an `#[deftly(newfn)]` attribute, and false otherwise. There are also `vmeta` and `fmeta` conditions to detect `#[deftly(..)]` attributes on variants and fields respectively. > Note: Don't confuse conditions with expansions! > As we use it here, `tmeta(x)` is a condition (true or false), > whereas `${tmeta(x) as ident}` is an expansion. > > They are similar > (for memorability and consistency), but > conditions and expansions have separate namespaces; > you cannot (in general) use them interchangeably. There is a [complete list of recognized conditions][ref:conditions] in the reference. ## Multiple exclusive options with `$select1` {#select1} Sometimes you want to make sure _exactly one_ condition matches. This is a good choice when you have multiple options for controlling some feature, and you want to make sure that the user has specified exactly one (or maybe, no more than one). You can do this using the `${select1}` keyword: `${select1 COND1 { ... } else if COND2 { ... } ... else { ... }` Its syntax is identical to `${if}`. But the behaviour is different: _all_ conditions are checked. If more than one condition is true, then derive-deftly rejects `${select1}`: it fails to expand, and emits an error. If no conditions are true, and there is no `else` clause, then `${select1}` is also rejected (it fails to expand, and emits an error). Here's an example of how we could use `${select1}` if we wanted to allow `constructor(default_name)`, as a synonym for our default behavior. In this case, we enforce that the user has specified `constructor(newfn(X))` _or_ `constructor(default_name)` _or_ neither, but not both: ```rust # use derive_deftly::define_derive_deftly; # define_derive_deftly! { # Constructor for struct: # # impl<$tgens> $ttype where $twheres { # pub fn ${select1 tmeta(constructor(newfn)) { ${tmeta(constructor(newfn))} } else if tmeta(constructor(default_name)) { new } else { new } } # () {} # } # } ``` [x:if]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:if [c:tmeta]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#c:tmeta [ref:conditions]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#conditions work/book/src/constructor-debugging.md0000664000000000000000000000610214763657303015326 0ustar # Debugging templates When writing complex templates, it can sometimes be hard to figure out compiler errors. `derive-deftly` has some features which can help: #### Syntax checking the expansion You can use the [`expect`][eo:expect] option to tell derive-deftly what your macro is supposed to produce: ```rust # use derive_deftly::{Deftly, define_derive_deftly}; define_derive_deftly! { Constructor for struct, expect items: impl<$tgens> $ttype where $twheres { // ... } } # #[derive(Deftly)] # #[derive_deftly(Constructor)] # struct Test; ``` If the expansion fails to syntax check, you'll get not only an error pointing at the part of the template or structure which seems wrong, but also error messages pointing into a *copy of the actual expansion*. Hopefully you'll be able to see what's wrong there. If your macro is supposed to expand to an expression, you can write `expect expr` instead of `expect items`. Alternatively, you can request this syntax check [when you invoke the macro][expansion-options]: ```rust # use derive_deftly::{Deftly, define_derive_deftly}; # define_derive_deftly! { Constructor: } #[derive(Deftly)] #[derive_deftly(Constructor[expect items])] struct MyStruct { /* ... */ } ``` > Note: The above section isn't really logical: "expect items" is always > the case for templates defined with `define_derive_deftly`: > only for adhoc templates is `expect expr` reasonable. > > TODO: Rewrite and move this section. #### Seeing a copy of the expansion Sometimes, the expansion is syntactically valid, but is wrong in some other way. You can use the [`dbg`][eo:dbg] option to tell derive-deftly to print a copy of the expansion. ```rust # use derive_deftly::{Deftly, define_derive_deftly}; # define_derive_deftly! { Constructor: } #[derive(Deftly)] #[derive_deftly(Constructor[dbg])] struct MyStruct { /* ... */ } ``` You'll see the compiler print something like this: ```ignore ---------- derive-deftly expansion of Constructor for MyStruct (start) ---------- impl<> MyStruct where { pub fn new(...) { ... } } ---------- derive-deftly expansion of Constructor for MyStruct (end) ---------- ``` Just as with the `dbg!` macro, you don't want to leave this in your production code. [eo:expect]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#eo:expect [expansion-options]: https://docs.rs/derive-deftly/latest/derive_deftly/derive.Deftly.html#expansion-options [eo:dbg]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#eo:dbg work/book/src/constructor-define.md0000664000000000000000000000717214763657303014635 0ustar # Managing complexity with $define Our constructor example has gotten fairly hard to read! Before we take it any further, let's refactor it to make it more clear what it's doing. Here, we will use the `$define` and `$defcond` keywords to define new aliases for some of our longer expansions and conditions. This will make our intent more clear, and hopefully easier to read. ```rust # use derive_deftly::define_derive_deftly; define_derive_deftly! { Constructor: // First, we define some aliases. // (1) ${define FUNC_NAME { ${if tmeta(constructor(newfn)) { ${tmeta(constructor(newfn)) as ident} } else { new }} }} // (2) ${defcond F_DFLT fmeta(constructor(default))} // (3) ${define F_INITIALIZER ${if F_DFLT { ::std::default::Default::default() } else { $fpatname }} } // Now that's out of the way, here is the template expansion: impl<$tgens> $ttype where $twheres { $tvis fn $FUNC_NAME( $( ${when not(F_DFLT)} $fpatname: $ftype , ) ) -> Self { Self { $( $fname: $F_INITIALIZER , ) } } } } # # use derive_deftly::Deftly; # #[derive(Deftly)] # #[derive_deftly(Constructor)] # struct Foo { # #[deftly(constructor(default))] # s: Vec, # n: u32, # } ``` In the example above, we've introduced some aliases for our trickier expressions, so that our template is easier to read. At `// (1)` and `// (3)`, we've used [`$define`][x:define] to introduce new expansions. The earlier one (`$FUNC_NAME`) is the name of our function; the later one (`$F_INITIALIZER`) is the expression that we use to initialize the current field. At `// (2)`, we've used [`$defcond`][x:defcond] to introduce a new condition, called `$F_DFLT`. It is true whenever we have been told to use the `Default` value for a the current field. The `$defcond` and `$define` keywords don't expand to anything themselves; their only affect is to create the interpretation of the keywords or conditions they define. > NOTE: To avoid name conflicts, > aliases created by `$define` and `$defcond` > must be in `ALL_CAPS`. > > As a general practice, it's a good idea > to prefix field-specific aliases with `F_` > and variant-specific aliases with `V_`. ## How does the scoping work here? You might have noted that, in our example above, we define our new keywords and conditionals at the start of our template. In this context, there is no "current field"— but two of our definitions use `fmeta`, which requires the existence of a current field. How does this work? In brief: the body of a definition is interpreted _at the point where it is used_. No matter where we put the `${define F_INITIALZER ...}` in our template, it will use the value of `${fmeta ...}` from the point where we actually use `$F_INITIALIZER`. [x:define]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:define [x:defcond]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:define work/book/src/constructor-intro.md0000664000000000000000000000422514763657303014532 0ustar # Example 2: Defining a constructor function In this section, we'll be using another example to demonstrate more of what `derive-deftly` can do. We'll be building a `Constructor` template to define a `new()` function for a struct, without having to write out all of its arguments. Let's start with the following (struct-only) template: ```rust # use derive_deftly::define_derive_deftly; define_derive_deftly! { Constructor: impl<$tgens> $ttype where $twheres { pub fn new( $( $fpatname: $ftype , ) ) -> Self { Self { $( $fname: $fpatname , ) } } } } ``` When you apply the above template to a type like this: ```rust # use derive_deftly::define_derive_deftly; # define_derive_deftly! { # Constructor: # # impl<$tgens> $ttype where $twheres { # pub fn new( $( $fpatname: $ftype , ) ) -> Self { # Self { # $( $fname: $fpatname , ) # } # } # } # } use derive_deftly::Deftly; #[derive(Deftly)] #[derive_deftly(Constructor)] struct Ex { a: f64, b: A, c: String } ``` You'll get a constructor like this: ```rust # struct Ex { a: f64, b: A, c: String } impl Ex { pub fn new( f_a: f64, f_b: A, f_c: String ) -> Self { Self { a: f_a, b: f_b, c: f_c } } } ``` So far, there aren't any new techniques at work here. We'll add some more down below. Note the use of `$fpatname` for the function arguments, rather than `$fname`. This makes the macro work with tuple structs: a tuple struct has field "names" like `0` and `1`, which aren't useable as variable names. work/book/src/constructor-limitations.md0000664000000000000000000000321514763657303015731 0ustar # Limiting a template to one kind of type. The `Constructor` template above doesn't work for enumerations. If you try to apply it to one, you'll get a not-entirely-helpful error message. In earlier examples, we've shown how to make templates that apply to enums as well as structs. But let's suppose that in this case, we want our template to be struct-only. We can tell derive-deftly about this restriction, to help it generate more useful error messages: ```rust # use derive_deftly::define_derive_deftly; define_derive_deftly! { Constructor for struct: // (1) // The rest is unchanged impl<$tgens> $ttype where $twheres { pub fn new( $( $fname: $ftype , ) ) -> Self { Self { $( $fname , ) } } } } ``` (Note the use of [`for struct`][eo:for] above at `// (1)`.) Now if we try to apply our template to an enum, we'll get a more useful error: ```text,ignore error: template defined for struct, but applied to enum ``` [eo:for]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#eo:for work/book/src/constructor-visibility.md0000664000000000000000000000357614763657303015576 0ustar # Working with visibility Our `Constructor` template above doesn't really make sense if it's applied to a non-public type; It would define the `new()` function as public, when the type is not meant to be exposed outside the crate. (Rust may even complain that we're declaring a public function that returns a private type!) Let's fix this, and have our template give our constructor the same visibility as the type itself: ```rust # use derive_deftly::define_derive_deftly; define_derive_deftly! { Constructor for struct: impl<$tgens> $ttype where $twheres { // (this "$tvis" is new) $tvis fn new( $( $fname: $ftype , ) ) -> Self { Self { $( $fname , ) } } } } ``` Here instead of saying `pub fn new`, we said `$tvis fn new`. The [`$tvis`][x:tvis] keyword will expand to the visibility of the top-level type. There is a similar keyword [`$fvis`][x:fvis] that expands to the visibility of the current field. (Since enum variants are always visible, there is no such keyword as `$vvis`. Since enum fields are always visible, `$fvis` in an enum always expands to `pub`.) [x:tvis]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:tvis [x:fvis]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:fvis work/book/src/difference-if-enum.md0000664000000000000000000001172414763657303014446 0ustar # A brute-force approach for applying to structs and enums One way we can solve [our problems](./difference-problems.md) with writing a `derive_deftly(Diff)` template is a brute-force approach. Here we just use [conditionals](constructor-conditionals.md) to write different versions of our template for the `struct` case and the `enum` case. To handle the difference between tuple and unit structs and variants, we can use conditionals as well. Here are the new conditions we'll need: * [`is_enum`][c:is_enum] - True if the top-level type is an enum. * [`is_struct`][c:is_struct] - True if the top-level type is a struct. * [`v_is_unit`][c:v_is_unit] - True if the current variant (or the top-level type, in the case of a struct) is a unit struct/variant. * [`v_is_tuple`][c:v_is_tuple] - True if the current variant (or the top-level type, in the case of a struct) is a tuple struct/variant. * [`v_is_named`][c:v_is_named] - True if the current variant (or the top-level type, in the case of a struct) is a struct/variant with named fields. With these conditions, we can write our template, albeit with quite a lot of redundancy. > Note 1: You may want to refresh your memory about > [`${select1}`](constructor-conditionals.md#select1) > and [`$`](paste-and-case.md). > > Note 2: We're ignoring generics in this example, for clarity. ```rust # pub trait Diff { type Difference: std::fmt::Debug; } # impl Diff for u32 { type Difference = (); } # use derive_deftly::{define_derive_deftly, Deftly}; define_derive_deftly!{ Diff: ${define DIFF_TYPE { Option< <$ftype as Diff>:: Difference> } } ${select1 is_struct { ${select1 v_is_unit { #[derive(Debug)] $tvis struct $<$tname Diff>; } else if v_is_tuple { #[derive(Debug)] $tvis struct $<$tname Diff>( ${for fields {$DIFF_TYPE , }} ); } else if v_is_named { #[derive(Debug)] $tvis struct $<$tname Diff> { $( $fvis $fname: $DIFF_TYPE, ) } }} // end select1 } else if is_enum { #[derive(Debug)] $tvis enum $<$tname Diff> { $( ${select1 v_is_unit { // nothing to do; we don't emit anything here. } else if v_is_tuple { $( ${for fields {$DIFF_TYPE , }} ), } else if v_is_named { $ { $( $fname: $DIFF_TYPE, ) }, }} // end select1 ) // end iteration over variants VariantChanged { original_value: $ttype, new_value: $ttype, }, } }} // end select1 impl Diff for $tname { type Difference = $<$tname Diff>; // ... and at this point, you still have to define // an implementation for `fn diff()`: good luck! } } # use derive_deftly_template_Diff; # #[derive(Clone,Debug,Deftly)] # #[derive_deftly(Diff)] # struct Unit; # #[derive(Clone,Debug,Deftly)] # #[derive_deftly(Diff)] # struct Tuple(u32, u32); # #[derive(Clone,Debug,Deftly)] # #[derive_deftly(Diff)] # struct Named { a: u32, b: u32 } # #[derive(Clone,Debug,Deftly)] # #[derive_deftly(Diff)] # enum Enum { Un, Tup(u32,u32), Nam { a: u32, b: u32 } } ``` This is a viable approach, but not a very maintanable one: look how we've had to copy out our definition separately for every possible Rust syntax! We managed to save some repetition with `$define`, but we can still do better. [c:is_enum]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#c:is_enum [c:is_struct]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#c:is_struct [c:v_is_unit]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#c:v_is_unit [c:v_is_tuple]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#c:v_is_tuple [c:v_is_named]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#c:v_is_named work/book/src/difference-intro.md0000664000000000000000000001123014763657303014231 0ustar # Example 3: Tracking the difference between objects So far we've seen some of `derive-deftly`'s features for defining different kinds of templates. In this section, we'll extend on what we've learned already to build a template to track the difference between objects. Unlike our previous examples, this template will define a new "difference" type that mirrors the structure of the type we're applying it to. Here's our motivating example. Let's suppose we have some complex structures and we want to expose the differences between those structures in a human-readable way. For example, we might want to write an `assert_eq!` test that shows only the actual changed fields between two instances, rather than showing their entire Debug outputs. > This example is inspired by the [`comparable`] crate, > which has a bunch of other useful features > we won't be replicating here. > If this code seems like something you'd want, > you should probably use `comparable` instead. To begin with, let's assume our crate has defined a `Diff` trait, looking something like: ```rust pub trait Diff { /// A type that describes the difference between /// two values of this type. type Difference: std::fmt::Debug; fn diff(&self, other: &Self) -> Option; } macro_rules! assert_no_difference { { $a:expr, $b:expr } => { match $a.diff($b) { Some(diff) => panic!("{} != {}: {:?}", stringify!($a), stringify!($b), diff), None => {}, } } } ``` We'll also assume that we've implemented our `Diff` trait on a wide array of types from `std`. What kind of behavior do we want here? When we write a template to derive `Diff` on a struct `MyStruct`, we'll want our template to define a new struct whose fields represent the changes between the old structure and the new. ```rust # pub trait Diff { type Difference: std::fmt::Debug; } # impl Diff for String { type Difference = (); } # struct SomeOtherStruct; # impl Diff for SomeOtherStruct { type Difference = (); } // Example struct struct MyStruct { a: String, pub b: SomeOtherStruct } // Expected output #[derive(Debug)] struct MyStructDiff { a: Option< ::Difference >, // Let's assume we want the same visibility. pub b: Option< ::Difference >, } ``` And when we write a template to derive `Diff` on an enum `MyEnum`, we'll want our template to define a new enumeration representing the change. ```rust # pub trait Diff { type Difference: std::fmt::Debug; } # impl Diff for String { type Difference = (); } # impl Diff for u32 { type Difference = (); } # #[derive(Debug,Clone)] struct SomeOtherStruct; # impl Diff for SomeOtherStruct { type Difference = (); } // Example enum #[derive(Clone, Debug)] enum MyEnum { A, B(String, String), C { v1: u32, v2: SomeOtherStruct, } } // Expected output #[derive(Debug)] enum MyEnumDiff { // We'll never actually generate this variant: // if two values are both `MyEnum::A`, they have no difference. BothA, /// Both items are MyEnum::B, but some value changed. BothB( Option< ::Difference >, Option< ::Difference >, ), // Both items are MyEnum::C, but some value changed. BothC { v1: Option< ::Difference >, v2: Option< ::Difference >, }, // The two items are different variants of MyEnum. // // (This assumes that MyEnum implements Debug and Clone.) VariantChanged { original_value: MyEnum, new_value: MyEnum, } } ``` Let's try to see how we can make this happen! [`comparable`]: https://crates.io/crates/comparable work/book/src/difference-problems.md0000664000000000000000000001014614763657303014726 0ustar # Some difficulties when creating our Diff type In the last section, we saw what we want our `derive_deftly(Diff)` template to do. Here we'll analyze a few problems that we need to solve in order to make it work. These problems mostly derive from the fact that we're trying to write a single template that can either generate a `struct` or an `enum`, depending on the type we're applying it to, but they all take different forms. ## Problem 1: What keyword do we use to define? {#kwd} We want an expansion that gives us `struct` for a struct and `enum` for an enum. While we could trivially build one with `${if is_struct {struct} else {enum}}`, it seems kind of verbose. ## Problem 2: How do we declare the generics? {#gen-defaults} When we define our new `FooDiff` struct, we want it to have the same generics that were used when we declared `Foo`. We might think of doing this with something like: ```rust,ignore struct $<$tname Diff><$tgens> where $twheres { ... } ``` And that might be good enough for many purposes, but it isn't quite right. Here's why: If `Foo` is defined as `struct Foo`, then `FooDiff` will only get defined as `struct Foo`. Thus the corresponding `Diff` type for `Foo` will not be `FooDiff`, but rather `FooDiff`. We need a way to declare generics _along with their defaults_. ## Problem 3: Extra braces on the enum case. {#enum-braces} For the enum case, we need our template to generate: ```rust,ignore enum FooDiff { Variant { ... }, Variant { ... }, ... } ``` But for the struct case, we want: ```rust,ignore struct FooDiff { ... } ``` Note that there are extra braces in the `enum` case. How can we generate those? (We can't use `${if}` to insert a single brace, since all the arguments to a `derive-deftly` expansion need to have balanced braces.) ## Problem 4: Defining our structs and variants {#defining} [Earlier](clone-enums.md) [we saw](clone-tuple-unit.md) that when constructure and destructing, we could pretend that tuple structs and tuple variants were really `braced` variants with members named `0`, `1`, `2`, and so on; and that we could pretend that unit structs and unit variants were just empty `braced` structs. But this approach doesn't work when _declaring_ a struct or variant: `struct Foo {}` is not the same as `struct Foo;`, and `struct Foo { 0: u32 }` is a syntax error! So we will need a different approach. We'll need to generate braces and field names if the struct/variant has named fields; we'll need to generate parentheses and no field names if the struct/variant is a tuple; and we'll need to generate nothing at all if we have a unit struct/variant. (Moreover, if we're generating a tuple struct or a unit struct, we'll need a trailing semicolon, but if we're generating any kind of a variant, we'll need a trailing comma.) ## Problem 5: field visibility works differently among structs and enums {#fvis-difference} We decided above that we wanted each "difference" field to have the same visibility as the original field in our struct and enum. [Earlier](constructor-visibility.md), we saw [`$fvis`][x:fvis] as a way to get the visibility of a field. But this won't work when we're declaring an `enum`: since every enum field is public, `$fvis` for an enum's fields always expands to `pub`, and Rust won't allow us to write ```rust,ignore enum FooDiff { Variant { pub a: u32; }, } ``` [x:fvis]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:fvis [x:tgens]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:tgens work/book/src/difference-solving.md0000664000000000000000000002227714763657303014574 0ustar # A better solution to our problems with Diff In previous sections, we've seen [a few problems](difference-problems.md) with writing a template that can define either a struct or an enum. We built a [brute-force solution](difference-if-enum.md) that was workable if not especially elegant. Here, we'll show a better way. We'll start by revisiting those problems; this time we'll show a better solution to each one. After we've gone over those solutions, we'll put them together into a working template. ## Revisiting our problems So, how can we make our template? ### Solving problem 1: `$tdefkwd` for a top-level keyword We needed [a way to get the right keyword](difference-problems.md#kwd) for our new type. This is available nicely as [`$tdefkwd`][x:tdefkwd] ("toplevel definition keyword"), which expands to `struct`, `union`, or `enum` as appropriate. ### Solving problem 2: `$tdefgens` for generics with defaults We needed a way to [declare generics along with their defaults](difference-problems.md#gen-defaults). This come in the form of [`$tdefgens`][x:tdefgens], which gives the generics from the top-level type along with their bounds _and_ their defaults. ### Solving problem 3: `$tdefvariants` to brace our variants as needed We needed some way to [wrap our enum variants in braces](difference-problems.md#enum-braces), but leave our structs unwrapped. derive-deftly solves this problem with the [`$tdefvariants`][x:tdefvariants] expansion, which adds braces only when the top-level type is an enum. In other words, `${tdefvariants ARG}` expands to `{ARG}` for an enum, but `ARG` otherwise. ### Solving problem 4: `$vdefbody` and `$fdefine` for struct/variant types We needed some way to make [appropriate type and field declarations](difference-problems.md#defining) no matter whether the current struct or variant is a unit struct/variant, a tuple struct/variant, or a struct with named fields/fields. We solve this problem in two parts. First, the [`$vdefbody`][x:vdefbody] expansion gives us the appropriate syntax to declare a variant or structure corresponding to the current variant/structure, with the necessary braces, parentheses, and/or trailing semicolon or comma. Specifically, `${vdefbody VNAME FIELDS}` will expand to one of: * `FIELDS;` for unit structs (though typically FIELDS will be empty) * `(FIELDS);` for tuple structs * `{FIELDS}` for structs with named fields * `VNAME FIELDS,` for a unit variant in an enum (though typically FIELDS will be empty) * `VNAME(FIELDS),` for a tuple variant in an enum * `VNAME { FIELDS },` for a variant with named fields in an enum. Second, to provide a field name and colon if appropriate, we use [`${fdefine NAME}`][x:fdefine], which expands to `NAME:` if fields are named, and to nothing otherwise. ### Solving problem 5: `$fdefvis` for visibility We needed some way to define [appropriate visibility](difference-problems.md#fvis-difference) for struct fields, given that `pub` isn't allowed to appear in an enum definition. For this, we use [`$fdefvis`][x:fdefvis], which expands to the visibility of the current field in a struct, and to nothing in an enum. ## Putting it all together This probably sounds like a lot, and admittedly it can be tricky to synthesize. As a template, it's handy to start by modifying this syntax, which just reproduces the original type with a new name. ```rust,ignore $tvis $tdefkwd $<$tname Copy><$tdefgens> ${tdefvariants $( ${vdefbody $vname $( $fdefvis ${fdefine $fname} $ftype, ) } ) } ``` Based on that, we produce the following. (As an added bonus, we'll define the actual `diff` method!) ```rust # pub trait Diff { type Difference: std::fmt::Debug; fn diff(&self, o:&Self) -> Option; } # impl Diff for u32 { type Difference = (); fn diff(&self,o:&Self) -> Option<()> {None} } # use derive_deftly::{define_derive_deftly, Deftly}; define_derive_deftly!{ Diff: ${if is_union {${error "Unions aren't supported"}}} ${define DIFF_TYPE { Option< <$ftype as Diff>:: Difference> } } #[derive(Debug)] $tvis $tdefkwd $<$tname Diff><$tdefgens> ${tdefvariants $( ${vdefbody $ $( $fdefvis ${fdefine $fname} $DIFF_TYPE , ) } ) ${if is_enum { VariantChanged { original_value: $ttype, new_value: $ttype, }, }} } impl<$tgens> $<$ttype Diff> where $twheres { /// Helper: Return this value if it reflects a real change. fn if_is_a_change(self) -> Option { match &self { $( // (1) ${vpat self=$<$ttype Diff> vname=$} => { if $( $fpatname.is_none() && ) true { return None; } } ) ${if is_enum { Self::VariantChanged{..} => {} }} } return Some(self); } } impl<$tgens> Diff for $ttype where ${if is_enum {$ttype: Clone,}} $twheres { type Difference = $<$ttype Diff>; fn diff(&self, other: &Self) -> Option { match (self, other) { $( // (2) ($vpat, ${vpat fprefix=other_}) => { // (3) ${vtype self=$<$ttype Diff> vname=$} { $( $fname : Diff::diff($fpatname, $), ) } } ) // End variants. ${if is_enum { (original_value, new_value) => { Self::Difference::VariantChanged { original_value: original_value.clone(), new_value: new_value.clone(), } } }} }.if_is_a_change() } } } # use derive_deftly_template_Diff; # #[derive(Clone,Debug,Deftly)] # #[derive_deftly(Diff)] # struct Unit; # #[derive(Clone,Debug,Deftly)] # #[derive_deftly(Diff)] # struct Tuple(u32, u32); # #[derive(Clone,Debug,Deftly)] # #[derive_deftly(Diff)] # struct Named { a: u32, b: u32 } # #[derive(Clone,Debug,Deftly)] # #[derive_deftly(Diff)] # enum Enum { Un, Tup(u32,u32), Nam { a: u32, b: u32 } } ``` With understanding, this approach should appear much terser and more logical than the many-cases approach from the [last section](difference-if-enum.md). We've introduced a few previously unseen expansions: let's talk about them now. At the beginning, we use [`$error`][x:error]. If it is ever expanded, then the whole template expansion is rejected with a compile-time error. We use it to make sure that nobody tries to apply our template to a `union`. We use [`$vpat`][x:vpat] in a new form. As discussed [in the reference][x:vpat], `$vpat` can take arguments to change its behavior. We use this feature twice: - At `// (1)` we use the `self` argument to make our pattern destructure the Diff object rather than the original top-level type, and we use `vname` to destructure a `$` variant rather than the original variant name. - At `// (2)`, we use the `fprefix` argument to give the second instance of our pattern a different set of binding names, so they will not conflict with the first instance. Finally, we pass arguments to [`$vtype`][x:vtype] at `// (3)`, so that instead of constructing an instance of the current variant, it constructs the `*Diff` type with an appropriate `Both*` name. [x:tdefkwd]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:tdefkwd [x:tdefvariants]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:tdefvariants [x:vdefbody]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:vdefbody [x:fdefine]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:fdefine [x:fdefvis]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:fdefvis [x:tdefgens]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:tdefgens [x:error]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:error [x:vpat]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:vpat [x:vtype]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:vtype work/book/src/expansions-naming.md0000664000000000000000000000327314763657303014454 0ustar # Naming conventions for derive-deftly expansions Here we discuss some naming conventions used in the naming of derive-deftly expansions. Many derive-deftly expansions' names start with [a single letter][keyword-initial-letters] to indicate what information they use: * `t` for **top-level** (the `struct`, `enum`, or `union` you are applying the template to), * `v` for **variant** (a variant of an `enum`), * or `f` for **field** (a single field of a struct or variant). For example, there is `$ttype` for "top-level type" and `$fname` for "field name". (We say "top-level" instead of "struct", since the "top-level" part of a declaration can also be an enum or a union.) Many derive-deftly expansions end with a short identifier for what they contain. For example, `$tname` is the name of a top-level type, `$vname` is the name of a variant, and `$fname` is the name of a field. Whenever possible (and logical), we have tried to use the same identifier for the `t`, `v`, and `f` cases. [keyword-initial-letters]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#keyword-initial-letters work/book/src/exporting-templates.md0000664000000000000000000000763214763657303015034 0ustar # Exporting templates But now suppose that you want to expose your template, so that people can use it from any crate. To do this, you use [`export` before the name of your macro][exporting-a-template]: ```rust pub trait HelloWorld { fn greet(); } derive_deftly::define_derive_deftly! { /// Derives `HelloWorld`, providing `greet` export HelloWorld: impl $crate::HelloWorld for $ttype { fn greet() { println!("Greetings from {}", stringify!($ttype)); } } } # fn main() {} ``` > Declaring our template as `export HelloWorld` in this way > is equivalent to marking `derive_deftly_template_HelloWorld` > with `#[macro_export]`. > Doing this puts the template into the root of the crate. > You can make the template visible elsewhere using `pub use`. Note that this time, we've defined `HelloWorld` as a trait, and we've changed our template to refer to that trait as `$crate::HelloWorld`. The [`$crate`][x:crate] syntax will expand to the name of the crate in which our template was defined, so that when later we expand this template in a _different_ crate, it will find the right trait. (Otherwise, it would fail if the `HelloWorld` trait were not in scope.) We've also added a [doc comment], which will appear in the public API documentation for our crate. Additionally, [we need to re-export `derive_deftly`][must-re-export] from our crate, so that when our template is applied to user types, a compatible derive-deftly engine is used. And we should also invoke [`derive_deftly::template_export_semver_check`] once, somewhere in our crate: this will tells us about any implications for your crate's semver, when you change your `Cargo.toml` to upgrade the `derive-deftly` crate. ```rust,ignore // At the root of our crate: #[doc(hidden)] pub use derive_deftly; // Use the current version of derive_deftly here: derive_deftly::template_export_semver_check!("0.11.0"); ``` Now, when somebody wants to use our template from a different crate, they can do it like this: ```rust,ignore // Let's pretend our crate is called hello_world. use hello_world::{ // This is the trait we defined... HelloWorld, // This is the macro that makes our template work. // (We might come up with a better syntax for this later). derive_deftly_template_HelloWorld }; use derive_deftly::Deftly; #[derive(Deftly)] #[derive_deftly(HelloWorld)] struct TheirStructure { // ... } ``` Note that exporting a template to other crates with `export` doesn't affect its visibility *within your crate*. You may still need `#[macro_use]`. ## What's next Now that we've explained how to expose a template, it's time to learn about a simpler syntax, for template we only want to use once. [`derive_deftly::template_export_semver_check`]: https://docs.rs/derive-deftly/latest/derive_deftly/macro.template_export_semver_check.html [must-re-export]: https://docs.rs/derive-deftly/latest/derive_deftly//macro.define_derive_deftly.html#you-must-re-export-derive_deftly-semver-implications [doc comment]: https://docs.rs/derive-deftly/latest/derive_deftly//macro.define_derive_deftly.html#docs-in-define [exporting-a-template]: https://docs.rs/derive-deftly/latest/derive_deftly//macro.define_derive_deftly.html#exporting-a-template-for-use-by-other-crates [x:crate]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:crate work/book/src/getting-started.md0000664000000000000000000000157314763657303014124 0ustar \[ _docs: [crate top-level]() | [overall toc, macros]() | [template etc. reference]() | [**guide/tutorial**]()_ \] # Getting started In this short chapter, we'll talk about a few things you should know before you start using `derive-deftly`, and we'll show you a simple "hello world" example. work/book/src/intro.md0000664000000000000000000001420214763657303012143 0ustar # Introduction: Using derive-deftly for easy derive macros. **derive-deftly** is a Rust package that you can use to define your own `derive` macros without having to write low-level procedural macros. The syntax is easy to learn, but powerful enough to implement macros of [significant](https://gitlab.torproject.org/nickm/build-deftly) [complexity](https://gitlab.torproject.org/nickm/serde-deftly). Just below is a simple example, to help you get a feel for the system. ### Reference documentation There is also comprehensive and rigorous reference material: * **[Template language reference manual][Reference]** (terse, but complete). * **[rustdoc top-level][rustdoc]**: Documents macros exposed by the `derive-deftly` crate. Also has links to all the other documentation. * **[Changelog, MSRV policy and cargo features][Changelog]**. ## Example: deriving field accessor functions Suppose you want to add accessor functions for the fields in your `struct`. You *could* do it by hand, like this: ```rust struct MyStruct { a: Vec, b: (u32, u32), c: Option, d: (u32, u32), // (more fields here ...) } impl MyStruct { fn get_a(&self) -> &Vec { &self.a } fn get_b(&self) -> &(u32, u32) { &self.b } fn get_c(&self) -> &Option { &self.c } fn get_d(&self) -> &(u32, u32) { &self.b } // (more accessors here...) } ``` But this is time consuming, and potentially error-prone. (Did you see the copy-paste error in `get_d`?) If you had to define a large number of accessors like this, or do it for a bunch of types, you might want an easier way. (Of course, in this case, you could use an [existing crate](https://crates.io/crates/derive-getters). But let's suppose that there was no crate meeting your needs, and you had to build your own.) Here's how you could define and use a derive-deftly template to save some time and risk. ```rust use derive_deftly::{define_derive_deftly, Deftly}; // Defines the template define_derive_deftly! { Accessors: // Here, $ttype will expand to the toplevel type; in this case, // "MyStruct". impl $ttype { // This $( ... ) block will be repeated: in this case, once per field. $( // Here we define a "get_" function: In this definition, // $ftype is the type of the field, // $fname is the name of the field, // and $<...> denotes token pasting. fn $(&self) -> &$ftype { &self.$fname } ) } } // Applies the template to your struct #[derive(Deftly)] #[derive_deftly(Accessors)] struct MyStruct { a: Vec, b: (u32, u32), c: Option, d: (u32, u32), // (more fields here...) } ``` > Note 1: > > This example is deliberately simplified for readability. > As written here, it only works for `struct`s > with no generic parameters. > Later on, we'll learn how to write templates that work > to `enum`s, generic types, and more. > Note 2: > > Some of the accessors above aren't the ones you'd write yourself > in idiomatic Rust: > You'd probably want to return `&str` instead of `&String`, > and `&[u8]` instead of `&Vec`. > > Once again, we'll be able to do this more idiomatically > once we're more familiar with `derive-deftly`. ## What, exactly, can derive-deftly do? The `derive-deftly` crate helps you do a lot of neat stuff: * You can define sophisticated _templates_ that apply, like `derive()` macros, to structs, enums, and unions. * You can _apply_ your templates to your own structs, enums, and unions. * You can _export_ your templates for use by other crates. * Your templates can _define_ new types, functions, methods, and variables: any kind of item that Rust allows. * Within your templates, you can look at _nearly everything_ about the input type: fields, variants, attributes, types, and so on. * Your templates can use _complex control structure_ to define configurable behavior or special cases. Still, there are a number of things that derive-deftly cannot do, or cannot do yet: * You can't apply a `derive-deftly` template to anything besides a struct, enum, or union. For example, you can't apply it to a `fn` declaration or an `impl` block. * Like a `derive` macro, a `derive-deftly` template cannot change the type it's applied to: You can define a _new_ type, but you can't change the definition of the original type. * The `derive-deftly` template language, though powerful, does not attempt to be a general-purpose programming language. There will always be some problems better solved through procedural macros, or through changes to the Rust language. * Because of limitations in the Rust macro system, `derive-deftly` templates have to be applied using the syntax above. (That is, in the example above, you need to say `#[derive(Deftly)]` and `#[derive_deftly(Accessors)]`. You can't define a macro that delivers `#[derive(Accessors)]` directly. ## About this book In the rest of this book, we'll explain how to use derive-deftly in detail. We'll try to explain each of its features, and how to use it to make correct, reliable templates that handle a broad range of situations. [Reference]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html [rustdoc]: https://docs.rs/derive-deftly/latest/derive_deftly/ [Changelog]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_changelog/index.html work/book/src/modularity.md0000664000000000000000000000212714763657303013204 0ustar # Using templates from other crates and modules In the previous section, we learned how to crate our first template. We used `derive-deftly` to define a template called `HelloWorld` that could print the name of a type. Before we go any further, we'll discuss how to use that template from other modules within the same crate, and how to expose that template so that other crates can use it. We'll also discuss a [convenient way] to define a template that only needs to be used once. [convenient way]: ./adhoc.md work/book/src/other-features.md0000664000000000000000000000357414763657303013757 0ustar # Other features `derive-deftly` has many more features, that aren't yet explained in this tutorial. For example: * [`fvis`][c:fvis], [`tvis`][c:tvis], and [`approx_equal`][c:approx_equal], more conditions for dealing with various cases by hand. * [`$tdeftype`][x:tdeftype] for defining a new data structure in terms of features of the input data structure, and [`$Xattrs`][x:fattrs] for passing through attributes. Full details are in the [reference], which also has a brief example demonstrating each construct. [reference]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html [c:fvis]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#c:fvis [c:tvis]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#c:tvis [c:approx_equal]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#c:approx_equal [x:tdeftype]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:tdeftype [x:fattrs]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:fattrs work/book/src/paste-and-case.md0000664000000000000000000001176214763657303013605 0ustar # Transforming names and strings Often, it's useful to define new identifiers based on existing ones, or to convert identifiers into strings. You _could_ use the existing [`paste`] crate for this, or you can use a native facility provided by derive-deftly. For example, suppose that you want to define a template that makes a "discriminant" type for your enumerations. You want the new type to be named `FooDiscriminant`, where `Foo` is the name of your existing type. While you're at it, you want to add an `is_` function to detect each variant. You can do that like this: ```rust # use derive_deftly::define_derive_deftly; define_derive_deftly! { Discriminant: #[derive(Copy,Clone,Eq,PartialEq,Debug)] enum ${paste $tname Discriminant} { $( $vname, ) } impl<$tgens> $ttype where $twheres { fn discriminant(&self) -> ${paste $tname Discriminant} { match self { $( $vpat => ${paste $tname Discriminant}::$vname, ) } } $( fn ${paste is_ ${snake_case $vname}} (&self) -> bool { self.discriminant() == ${paste $tname Discriminant} ::$vname } ) } } ``` Here we see a couple of new constructs. First, we're using [`${paste}`][x:paste] to glue several identifiers together into one. When we say `${paste $tname Discriminant}`, we are generating a new identifier from `$tname` (the type name) and the word Discriminant. So if the type name is `Foo`, the new type will be called `FooDiscriminant`. Second, we're using [`${snake_case}`][case-changing] to transform an identifier into `snake_case` (that is, lowercase words separated by underscores). We use this to turn the name of each variant (`$vname`) into a name suitable for use in a function name. So if a variant is called `ExampleVariant`, `${snake_case $vname}` will be `example_variant`, and `${paste is_ ${snake_case $vname}}` will be `is_example_variant`. There are [other case-changers][case-changing]: * `${pascal_case my_ident}` becomes `MyIdent`. You can also write this as `${upper_camel_case ..}`. * `${lower_camel_case my_ident}` becomes `myIdent`. * `${shouty_snake_case MyIdent}` becomes `MY_IDENT`. * `${snake_case MyIdent}` becomes `my_ident`, as you've already seen. You can abbreviate `${paste ...}` as `$<...>`. #### Pasting onto types Here's a side-note: You can use `${paste}` to append identifiers to a type (like `$ttype` or `$ftype`), not just an identifier. When you do this, `${paste}` will [do the right thing][identifier-pasting] even if the type is generic. For example, if `$ftype` is `Vec`, then `${paste $ftype Builder}` will expand to `VecBuilder`, not `VecBuilder`. #### A note on syntax In this last section, you've seen a new syntax for the first time. Both `${paste ident ident..}` and `${snake_case ident}` are special cases of the following meta-syntax, which derive-deftly uses everywhere: [`${KEYWORD ARGS.. }`][arg-syntax] In fact, if you want, you can use this format for all of the expansion macros you have already seen: `$ttype` is just a shortened form for `${ttype}`, `$fname` is just `${fname}`, and so on. Some keywords, including some of those we've already seen, can take named arguments. The syntax for this is: `${KEYWORD ARGNAME=VALUE ARGNAME=VALUE...}` > For example, we can use this syntax to give optional arguments to `$vpat`; > see the template syntax reference for more information. If you ever need to write a literal `$` (say, if you want to confuse yourself by making derive-deftly-generated pattern macros) you can write [`$$`][dollar-dollar]. [`paste`]: https://docs.rs/paste/latest/paste/ [dollar-dollar]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#dollar-dollar [x:paste]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:paste [case-changing]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#case-changing [identifier-pasting]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#x:paste [arg-syntax]: https://docs.rs/derive-deftly/latest/derive_deftly/doc_reference/index.html#named-and-positional-template-arguments-to-expansions-and-conditions work/book/src/templates-in-modules.md0000664000000000000000000000605014763657303015062 0ustar # Templates inside modules When you define a template, derive-deftly turns it into a definition of a `macro_rules!` macro. In the example above, that would be `derive_deftly_template_HelloWorld`. Unfortunately, [Rust's rules for scoping][macro-scoping] of `macro_rules!` macros are awkward and confusing. In general, there are two ways to expose a macro; we'll go over how to use each one for a derive-deftly template. ## With path-scoped macros { #path-scope } With modern versions of Rust (Rust 2018 and later), you can use [path-based scope] to define the scope of a macro. With this method, you use `pub use` (or `pub(crate) use`, etc) to define the scope for a macro, and allow it to be accessed by its path. ```rust pub mod hello_world { use derive_deftly::define_derive_deftly; define_derive_deftly! { HelloWorld: /* ... */ } pub(crate) use derive_deftly_template_HelloWorld; } mod caller { use derive_deftly::Deftly; use super::hello_world::derive_deftly_template_HelloWorld; #[derive(Deftly)] #[derive_deftly(HelloWorld)] pub struct MyStruct; // Alternatively, you use the path, and avoid having to say "use": #[derive(Deftly)] #[derive_deftly(super::hello_world::HelloWorld)] pub struct MyOtherStruct; } # fn main() {} // so that the above is not wrapped in fn main(). ``` This is generally the better method to use. ## Using `#[macro_use]` We can also expose a macro using [textual scope] and [`macro_use`]: We prefix that module's `mod` statement with `#[macro_use]`. If you do this, the module defining the macro must come before the module where you use the macro. (Also, the macro's name is never scoped within the module.) ```rust #[macro_use] // make HelloWorld visible outside the module (within the crate) mod hello_world { use derive_deftly::define_derive_deftly; define_derive_deftly! { HelloWorld: /* ... */ } } mod caller { // must come after mod hello_world use derive_deftly::Deftly; #[derive(Deftly)] #[derive_deftly(HelloWorld)] // not hello_world::HelloWorld pub struct MyStruct; } ``` [macro-scoping]: https://doc.rust-lang.org/reference/macros-by-example.html#scoping-exporting-and-importing [textual scope]: https://doc.rust-lang.org/reference/macros-by-example.html#textual-scope [`macro_use`]: https://doc.rust-lang.org/reference/macros-by-example.html#the-macro_use-attribute [path-based scope]: https://doc.rust-lang.org/reference/macros-by-example.html#path-based-scope work/book/tests/0000775000000000000000000000000014763657303011042 5ustar work/book/tests/Cargo.toml0000664000000000000000000000051714763657303012775 0ustar [package] name = "derive-deftly-book-tests" version = "0.1.0" edition = "2021" publish = false # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] path = "book-tests.rs" [dependencies] derive-deftly = { version = "*", path = "../.." } [build-dependencies] anyhow = "1" regex = "1" work/book/tests/book-tests.rs0000664000000000000000000000123514763657303013503 0ustar //! This crate exists as a place from which to run the doctests //! in our mdbook. //! //! (mdbook has an `mdbook test` subcommand, but it does badly with //! dependencies. See //! .) //! //! Use the `update_tests.sh` script to revise the `doctests` module. // This is not real rustdoc; we don't expect the links to work. // They will refer to things by their mdbook paths, not by // rustdoc identities. #![allow(rustdoc::broken_intra_doc_links)] #[allow(unused)] mod prelude { pub use derive_deftly::{define_derive_deftly, Deftly}; } mod chapters { include!(concat!(env!("OUT_DIR"), "/book_chapters.rs")); } work/book/tests/build.rs0000664000000000000000000000446314763657303012516 0ustar // build script: regenerate chapters.rs #![allow(clippy::style, clippy::complexity, clippy::perf)] use std::{ env, fs, io::Write as _, path::{Path, PathBuf}, }; const WARNING: &str = "\ // This file is automatically generated by build.rs. // // Do not edit by hand. "; fn modname(p: &Path) -> String { let m = p.file_stem().unwrap().to_str().unwrap(); m.to_ascii_lowercase().replace("-", "_") } /// Return a list of the md files linked from `inp`, relative to location of `inp`. /// /// Assumes that `inp` has the structure of a SUMMARY.md file, and so /// ignores everything but `[]()` links. fn find_md_files(inp: &Path) -> anyhow::Result> { // I would prefer to use `pulldown-cmark` here, but it doesn't // work with our MSRV. :P let re = regex::Regex::new(r"\]\((.*\.md)\)")?; let contents = fs::read_to_string(inp)?; Ok(re .captures_iter(&contents) .map(|c| PathBuf::from(c.get(1).unwrap().as_str())) .collect()) } fn main() -> anyhow::Result<()> { let tests_dir: PathBuf = env::var("CARGO_MANIFEST_DIR").unwrap().into(); let out_dir: PathBuf = env::var_os("OUT_DIR").unwrap().into(); let book_dir = tests_dir.parent().unwrap(); let book_src_dir = book_dir.join("src"); let summary_md_path = book_src_dir.join("SUMMARY.md"); // Find all the .md files. let md_files = find_md_files(&summary_md_path)?; // Tell cargo to re-run this script if there is a change to: // - the script itself, // - the SUMMARY.md file, // - any of the MD files we found. println!("cargo::rerun-if-changed=build.rs"); println!("cargo::rerun-if-changed={}", summary_md_path.display()); for fname in md_files.iter() { println!( "cargo::rerun-if-changed={}", book_src_dir.join(fname).display() ); } // Regenerate chapters.rs. let chapters_path = out_dir.join("book_chapters.rs"); let mut f = fs::File::create(chapters_path).unwrap(); writeln!(f, "{}", WARNING)?; for fname in md_files { writeln!( f, r#" #[doc = include_str!( concat!( env!("CARGO_MANIFEST_DIR"), "/../src/{}" ) )]"#, fname.display() )?; writeln!(f, "mod {} {{}}", modname(fname.as_path()))?; } Ok(()) } work/debian/0000775000000000000000000000000014763657303010170 5ustar work/debian/.gitignore0000664000000000000000000000021314763657303012154 0ustar .debhelper files debhelper-build-stamp substvars *.substvars librust-derive-deftly-dev librust-derive-deftly-doc cargo_home cargo_registry work/debian/README.source0000664000000000000000000000107514763657303012352 0ustar This package is maintained using the dgit-maint-merge(7) workflow. The Debian delta typically contains only things to override the upstream build system. This package uses the dh-rust style Rust packaging, putting both crates in the same .deb. The prinipal source repository is the dgit git server - use dgit clone, and when uploading simply dgit push. The source package is 1.0 native. We would like to use 3.0 native, but we can't do that with the non-native version number. #737634. Some violence has been done to allow st least some of the upstream tests to run. work/debian/changelog0000664000000000000000000000140714763657303012044 0ustar rust-derive-deftly (1.0.1-1) unstable; urgency=medium * New upstream version 1.0.1. * Redo packaging using dh-rust single-workspace approach. - No longer maintained within the Debian Rust Team. - Single Debian source package for both cargo packages. - Derived from upstream git, not crates.io - New rust-update-control script for helping with metadata maintenance. * d/copyright: Transfer fixed copyright years from upstream LICENCE -- Ian Jackson Mon, 10 Mar 2025 21:50:59 +0000 rust-derive-deftly (1.0.0-1) unstable; urgency=medium * Package derive-deftly 1.0.0 from crates.io using debcargo 2.7.7 * Closes: #1096062. -- Ian Jackson Sun, 02 Mar 2025 13:21:40 +0000 work/debian/control0000664000000000000000000000365614763657303011605 0ustar Source: rust-derive-deftly Section: rust Priority: optional Build-Depends: debhelper-compat (= 13), dh-sequence-rust, libtoml-perl, # @@ rust-update-control manages these: librust-anyhow+default-dev (>= 1~), librust-easy-ext+default-dev (>= 1~), librust-educe+default-dev (>= 0.4.6~), librust-glob+default-dev (>= 0.3~), librust-heck+default-dev (>= 0.4~), librust-indexmap+default-dev (>= 1.8~), librust-itertools+default-dev (>= 0.10.1~), librust-paste+default-dev (>= 1~), librust-proc-macro-crate+default-dev (>= 1.1.3~), librust-proc-macro2+default-dev (>= 1.0.53~), librust-quote+default-dev (>= 1~), librust-regex+default-dev (>= 1~), librust-sha3+default-dev (>= 0.10~), librust-static-assertions+default-dev (>= 1~), librust-strum+default-dev (>= 0.24~), librust-strum+derive-dev (>= 0.24~), librust-syn+default-dev (>= 2.0.53~), librust-syn+extra-traits-dev (>= 2.0.53~), librust-syn+full-dev (>= 2.0.53~), librust-toml+default-dev (>= 0.5.0~), librust-trybuild+default-dev (>= 1.0.46~), librust-void+default-dev (>= 1~), Maintainer: Ian Jackson Standards-Version: 4.7.0 Vcs-Git: https://git.dgit.debian.org/derive-deftly.git Vcs-Browser: https://browse.dgit.debian.org/derive-deftly.git Homepage: https://gitlab.torproject.org/Diziet/rust-derive-deftly Rules-Requires-Root: no Package: librust-derive-deftly-dev Architecture: all Multi-Arch: foreign Depends: ${rust-update-control:Depends}, ${misc:Depends} Provides: librust-derive-deftly+default-dev (= ${binary:Version}), # @@ rust-update-control manages these: librust-derive-deftly-1.0.1-dev (= ${binary:Version}), librust-derive-deftly-macros-1.0.1-dev (= ${binary:Version}), Suggests: librust-derive-deftly-doc Description: Ergonomic way to write derive() macros - Rust source code Uncompiled Rust library "derive-deftly" Conflicts: librust-derive-deftly-macros-dev (<= 1.0.0-1) Replaces: librust-derive-deftly-macros-dev (<= 1.0.0-1) work/debian/copyright0000664000000000000000000000306014763657303012122 0ustar Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: derive-deftly Upstream-Contact: Ian Jackson Source: https://gitlab.torproject.org/Diziet/rust-derive-deftly Files: * Copyright: 2022 onwards Ian Jackson 2022 onwards Tor Project Inc License: MIT Files: debian/* Copyright: 2025 Debian Rust Maintainers 2025 Ian Jackson License: MIT License: MIT Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: . The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. . THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. work/debian/copyright.debcargo.hint0000664000000000000000000000456414763657303014642 0ustar Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: derive-deftly Upstream-Contact: Ian Jackson and the contributors to Rust derive-deftly Source: https://gitlab.torproject.org/Diziet/rust-derive-deftly Files: * Copyright: FIXME (overlay) UNKNOWN-YEARS Ian Jackson FIXME (overlay) UNKNOWN-YEARS and the contributors to Rust derive-deftly License: MIT Comment: FIXME (overlay): Since upstream copyright years are not available in Cargo.toml, they were extracted from the upstream Git repository. This may not be correct information so you should review and fix this before uploading to the archive. Files: DEVELOPER-CERTIFICATE Copyright: 2004, 2006 The Linux Foundation and its contributors. License: UNKNOWN-LICENSE; FIXME (overlay) Comment: FIXME (overlay): These notices are extracted from files. Please review them before uploading to the archive. Files: LICENCE Copyright: 2022 Ian Jackson and contributors License: UNKNOWN-LICENSE; FIXME (overlay) Comment: FIXME (overlay): These notices are extracted from files. Please review them before uploading to the archive. Files: debian/* Copyright: 2025 Debian Rust Maintainers 2025 Ian Jackson License: MIT License: MIT Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: . The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. . THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. work/debian/debcargo.toml0000664000000000000000000000103414763657303012631 0ustar overlay = "." uploaders = ["Ian Jackson "] collapse_features = true # derive-deftly requires that you enable at leaast `minimal-1` # So the other "individual feature" tests are all broken. packages."lib" .test_is_broken = true packages."lib+minimal-1" .test_is_broken = false packages."lib+default" .test_is_broken = false packages."lib+full-msrv-1.56".test_is_broken = false packages."lib+full" .test_is_broken = false packages."lib+@" .test_is_broken = false work/debian/rules0000775000000000000000000000113614763657303011251 0ustar #!/usr/bin/make -f %: dh $@ execute_before_dh_gencontrol: debian/rust-update-control dh-gencontrol librust-derive-deftly-dev # "cargo package" includes the whole tree, but this is a .deb for building # against. We don't need all this stuff in our -dev .deb. We'll put the # formatted documentation in -doc. execute_after_dh_auto_install: set -e; cd debian/librust-derive-deftly-dev; \ cd usr/share/cargo/registry/derive-deftly-1.0.1; \ rm Cargo.lock.minimal; \ rm -r book maint playground # set -e; cd debian/librust-derive-deftly-dev; \ cd usr/share/cargo/registry/derive-deftly-macros-1.0.1 work/debian/rust-update-control0000775000000000000000000001360514763657303014056 0ustar #! /usr/bin/perl -w # # 1. Updates d/control. # 2. Updates substvars to define `${rust-update-control:Depends}` # # TODO rust-update-control/dh-rust: document or steal/adapt this thing! use strict; use Data::Dumper; use Dpkg::Substvars; use Getopt::Long; use TOML; our $mode; our @tomls; our @updateish_outputs; our $marker_re = qr{^\# \@\@ rust-update-control }; sub add_toml_1 ($) { my ($file) = @_; open C, $file or die "$file $!"; my $toml = do { local $/ = undef; // die "$file $!"; }; C->error and die "$file $!"; $toml = from_toml($toml) || die "$file ?"; push @tomls, $toml; return $toml; } our (%depends, %test_depends); our (@provides); sub convert_version ($) { local ($_) = @_; # Ignore upper version bounds. We'll detect any failures # due to newer versions in usual Debian QA (ci.debian.net etc.) s{^\>= ?([^,]+)\, \<[^,]+$}{$1}; if (m{^=? ?\d+(?:\.\d+){0,2}$}) { return "(>= $_~)"; } else { warn "don't know how to convert Cargo version spec \`$_`"; } } sub process_deps_table ($$$) { my ($toml, $key, $ofield) = @_; my $table = $toml->{$key}; return unless $table; my $our_crate = $toml->{package}{name}; foreach my $crate (sort keys %$table) { # Ignore dependencies pointing into the workspace next if grep { $_->{package}{name} eq $crate } @tomls; my $info = $table->{$crate}; if (!ref $info) { $info = { 'version' => $info }; } my $version = $info->{version} // die "$our_crate -$key-> $crate no version\n"; $version = convert_version($version); $version = " $version" if length $version; my @feats = @{ $info->{features} // [] }; unshift @feats, 'default' if $info->{'default-features'} // 1; my $pbase = $crate; $pbase =~ y/_/-/; my $add_dep = sub { my ($suffix) = @_; $ofield->{"librust-$pbase$suffix-dev$version"} = 1; }; $add_dep->("+$_") foreach @feats; $add_dep->('') if !@feats; } } sub add_deps ($) { my ($toml) = @_; my $crate = $toml->{package}{name}; print "processing $crate\n"; process_deps_table($toml, 'dependencies', \%depends); process_deps_table($toml, 'build-dependencies', \%depends); process_deps_table($toml, 'dev-dependencies', \%test_depends); } sub add_provides ($) { # TODO dh-rust: We don't really want to Provide -macros; upstream expects # dependers to use only the `derive-deftly` facade crate. # But this is needed to cause dh-rust to include the -macros package. my ($toml) = @_; return if ($toml->{package}{publish} // 'true') eq 'false'; my $p = $toml->{package}{name}; $p =~ s/_/-/g; my $v = $toml->{package}{version}; # Upstream package has some cargo features for reducing deps. We don't # support those here yet. We could do if another pakcage wanted them. # # TODO dh-rust: We don't really want to Provide -macros; upstream expects # dependers to use only the `derive-deftly` facade crate. # But this is needed to cause dh-rust to include the -macros package. # dh-rust (or we) really ought to be generating this as a substvar, not # *reading* it out of d/control's Provides field. push @provides, "librust-$p-$v-dev (= \${binary:Version})" } sub add_tomls () { my $toml = add_toml_1('Cargo.toml'); foreach my $subdir ($toml->{workspace}{members}->@*) { add_toml_1("$subdir/Cargo.toml"); } } sub read_calculate () { add_tomls(); add_deps($_) foreach @tomls; add_provides($_) foreach @tomls; } sub no_args () { die "$0: no further arguments/options allowed after mode $mode\n" if @ARGV; } sub mode_dumper () { no_args(); read_calculate(); print Dumper(\%depends, \%test_depends); } sub write_1_new_control ($$) { my ($file, $field_data) = @_; push @updateish_outputs, $file; my $any = 0; my $field; open I, "$file" or die $!; open O, ">$file.new" or die $!; while () { if (m/$marker_re/...!m/^[ \t]/) { if (m/$marker_re/) { $any++; } elsif (m/^[ \t]/) { next; } else { foreach (@{ $field_data->{$field} // die "$field ?" }) { print O " $_,\n" or die $!; # TODO } } } $field = lc $1 if m{^([-0-9a-z]+):}i; print O or die $!; } I->error and die $!; O->error and die $!; close O or die $!; die "$0: missing marker in $file\n" unless $any; } sub write_new_controls () { write_1_new_control('debian/control', { 'build-depends' => [ sort keys %depends ], 'provides' => \@provides, }); } sub update_substvars ($) { my ($file) = @_; my $sv = Dpkg::Substvars->new($file); $sv->set( 'rust-update-control:Depends', join ' ', map { "$_," } sort keys %depends ); $sv->save("$file.new"); rename "$file.new", $file or die $!; } sub check_diffs () { my @diffs; foreach my $file (@updateish_outputs) { $!=0; $?=0; my $r = system (qw(diff -u), "$file", "$file.new"); if (!$r) { unlink "$file.new" or die "$file $!"; } elsif ($r == 256) { push @diffs, $file; } else { die "diffing $file failed $? $!\n"; } } foreach my $file (@diffs) { print STDERR "$0: $file changed, needs refresh!\n"; } return @diffs; } sub mode_dh_gencontrol () { GetOptions() && @ARGV==1 or die "$0: mode dh-gencontrol wants no options, jsut pkg"; my ($pkg) = @ARGV; my $sv_file = "debian/$pkg.substvars"; read_calculate(); write_new_controls(); update_substvars($sv_file); my @diffs = check_diffs(); if (@diffs) { print STDERR "$0: warning: file(s) changed, refresh needed!\n"; } } sub mode_refresh () { no_args(); read_calculate(); write_new_controls(); foreach my $file (@updateish_outputs) { rename "$file.new", "$file" or die "$file: $!"; print "$file refreshed\n"; } } GetOptions() or die "$0: bad arguments/options\n"; $mode = shift @ARGV // die "$0: need mode argument\n"; my $mode_fn = $mode; $mode_fn =~ y/-/_/; $mode_fn = ${*::}{"mode_$mode_fn"}; $mode_fn or die "$0: unknown mode $mode\n"; $mode_fn->(); work/debian/source/0000775000000000000000000000000014763657303011470 5ustar work/debian/source/format0000664000000000000000000000000414763657303012675 0ustar 1.0 work/debian/tests/0000775000000000000000000000000014763657303011332 5ustar work/debian/tests/cargo-test-derive-deftly-tests0000775000000000000000000000121414763657303017227 0ustar #!/bin/bash set -euo pipefail cp -ar tests "$AUTOPKGTEST_TMP"/. cd "$AUTOPKGTEST_TMP" mkdir .cargo cat <<'END' >.cargo/config.toml [source.debian-packages] directory = "/usr/share/cargo/registry" [source.crates-io] replace-with = "debian-packages" END HOME="$AUTOPKGTEST_TMP" export HOME cd tests perl -i -ne ' # neither TOML, nor TOML::Tiny, can produce a working edited Cargo.toml s{\bpath *= *"[^"]+",}{} if m/^derive-deftly /; print or die $!; print "\n[workspace]\n" or die $! if eof; ' Cargo.toml # These can't sensibly be made to do as-installed, # and it's not what they're for. > directly.rs cargo test --features=full work/debian/tests/control0000664000000000000000000000021114763657303012727 0ustar Tests: cargo-test-derive-deftly-tests Depends: @, rustc, cargo, librust-derive-deftly-dev, libtoml-tiny-perl Restrictions: allow-stderr work/doc/0000775000000000000000000000000014763657303007513 5ustar work/doc/NOTES.md0000777000000000000000000000000014763657303013631 2../macros/NOTES.mdustar work/doc/implementation.md0000664000000000000000000002615314763657303013071 0ustar # Implementation approach - how does this work? **You do not need to understand this in order to use derive-deftly.** Also, you should not rely on the details here. They don't form part of the public interface. ## Introduction It is widely understood that proc macro invocations cannot communicate with each other. (Some people have tried sneaking round the back with disk files etc. but this can break due to incremental and concurrent compilation.) But, a proc macro can *define a `macro_rules` macro*. Then, later proc macros can invoke that macro. The expansion can even invoke further macros. In this way, a proc macro invocation *can* communicate with subsequent proc macro invocations. (This trick is also used by [`ambassador`](https://crates.io/crates/ambassador).) There is a further complication. One macro X cannot peer into and dismantle the expansion of another macro Y. (This is necessary for soundness, when macros can generate `unsafe`.) So we must sometimes define macros that apply other macros (whose name is supplied as an argument) to what is conceptually the first macro's output. ## Implementation approach - reusable template macros ### Expansion chaining When a `#[derive(Deftly)]` call site invokes multiple templates, we don't emit separate macro calls for each template. Instead, we generate a macro call for the first template, and pass it the names of subsequent templates. Each template expansion is responsible for invoking the next. This allows output from the expansion to be threaded through the chain, collecting additional information as we go, and eventually analysed after *all* the expansions are done. (This is done by putting`derive_deftly_engine` as the final entry in the list of macros to chain to.) Currently, this data collection is used for detecting unused meta attributes. This does mean that specifying a completely unknown template (for example, one that's misspelled or not in scope) will prevent error reports from subsequent templates, which is a shame. ### Overall input syntax for `derive_deftly_engine!` and templates Because of expansion chaining, calls to the engine, and to templates, overlap. ```rust,ignore macro! { { DRIVER.. } [ 1 0 AOPTIONS.. ] // not present for d_d_dengine expanding ad-hoc // replaced with just "." at end of chain // always present, but always immediately ignored: ( .... ) // these parts passed to d_d_engine only, and not in last call: { TEMPLATE.. } ( CRATE; [TOPTIONS..] TEMPLATE_NAME /* omitted if not available */; .... ) // always present: // after here is simply passed through by templates: [ // usually one or more of these; none at end, or ad-hoc CHAIN_TO ( CHAIN_PASS_AFTER_DRIVER .... ) .. ] [ACCUM..] .... } ``` (We use the notation `....` for room we are leaving for future expansion; these are currently empty.) ### 1. `define_derive_deftly!` macro for defining a reuseable template Implemented in `define.rs::define_derive_deftly_func_macro`. When used like this ```rust,ignore define_derive_deftly! { MyMacro TOPTIONS..: TEMPLATE.. } ``` Expands to ```rust,ignore macro_rules! derive_deftly_template_Template { { { $($driver:tt)* } [ $($aoptions:tt)* ] ( $($future:tt)* ) $($tpassthrough:tt)* } => { derive_deftly_engine! { { $($driver)* } [ $(aoptions)* ] ( ) { TEMPLATE.. } ( $crate; [TOPTIONS..] Template; ) $($tpassthrough)* } } } ``` Except, every `$` in the TEMPLATE is replaced with `$orig_dollar`. This is because a `macro_rules!` template is not capable of generating a literal in the expansion `$`. (With the still-unstable [`decl_macro`](https://github.com/rust-lang/rust/issues/83527) feature, `$$` does this.) `macro_rules!` simply passes through `$orig_dollar` (as it does with all unrecognised variables), and the template expansion engine treats `$orig_dollar` as just a single `$`. (The extra `( )` parts after the driver and template include space for future expansion.) ### 2. `#[derive_deftly(Template)]`, implemented in `#[derive(Deftly)]` Template use is implemented in `derive.rs::derive_deftly`. This ```rust,ignore #[derive(Deftly)] #[derive_deftly(Template[AOPTIONS..], Template2)] pub struct StructName { .. } ``` Generates: ```rust,ignore derive_deftly_template_Template! { { #[derive_deftly(..)] struct StructName { .. } } [ 1 0 AOPTIONS ] ( ) [ derive_deftly_template_Template2 ( [1 0 AOPTIONS2] () ) derive_deftly_engine ( . () ) ] [] // (nothing accumulated yet) } ``` ### 3. Actual expansion The first call to `derive_deftly_template_Template!` is expanded according to the `macro_rules!` definition, resulting in a call to `derive_deftly_engine`: ```rust,ignore derive_deftly_engine! { { #[derive_deftly(..)] pub struct StructName { .. } } [ 1 0 AOPTIONS ] ( ) { TEMPLATE.. } ( $crate; [TOPTIONS..] Template; ) [ derive_deftly_template_Template2 ( [1 0 AOPTIONS2] () ) derive_deftly_engine ( . () ) ] [] // ACCUM } ``` Implemented in `engine.rs::derive_deftly_engine_func_macro`, this performs the actual template expansion. ### 4. Chaining to the next macro `derive_deftly_engine!` then invokes the next template. In the above example, it outputs: ```rust,ignore derive_deftly_template_Template2! { { #[derive_deftly(..)] pub struct StructName { .. } } [ 1 0 AOPTIONS2 ] ( ) // threaded through from the call site [ derive_deftly_engine ( . () ) ] [_name Template _meta_used [..] _meta_recog [..]] // ACCUM } ``` `ACCUM` accumulates information from successive expansions, and is actually parsed only in the next step. #### Used meta attribute syntax `_meta_used` introduces the meta attributes from the driver, which were used by this template, in the following syntax: ```text ::VARIANT // entries until next :: are for this variant .field // entries until next :: or . are for this field ( // each (..) corresponds to one #[deftly()], and matches its tree somemeta( value_used =, // value was used bool_tested ?, // boolean was tested , // skip a node (subtree) not used at all .. // trailing skipped notes are omitted ) ) // empty trailing groups are omitted ``` So for example this ```rust,ignore struct S { #[adhoc(foo(bar, baz))] #[adhoc(wombat, zoo = "Berlin")] f: T, } ``` might correspond to this ```text _meta [ // nothing (don't need field name even) .f () (, zoo?) .f () (wombat?, zoo=) .f (foo=(bar?, baz?)) (wombat?, zoo?) ] ``` This representation helps minimise the amount of text which needs to be passed through all the macro chains, when only a few attributes are used by each template, while still being unambiguous and detecting desynchronisation. Instead of a `[..]` list, `_meta_used` can also be `*`, meaning that no checking should be done. ### Recognised meta attribute syntax `_meta_recog` introduces the meta attributes which might conceivably be recognised by this template. This is determined statically, by scanning the template. The information is used only if there are unused attributes, to assist with producing good error messages. `_meta_recog` takes a `[ ]` list containing items like `fmeta(foo(bar))`, or ``fmeta(foo(bar))?` when recognised only as a boolean. ### 5. Reporting unused meta attributes At the end of the list of chained templates, is (the driver's version of) `derive_deftly_engine!`. So after all the templates have been processed, instead of invoking the next template, we return directly to the engine: ```rust,ignore derive_deftly_engine! { { #[derive_deftly(..)] pub struct StructName { .. } } . ( ) [] // end of the chain [_name Template _meta_used [..] _meta_recog [..] _name Template2 ..] } ``` The accumulated parts are all of the form: `KEYWORD DATA`; `_KEYWORD DATA` for a part which can be safely ignored, if unrecognised. `DATA` is a single tt. Each template's parts start with a `_name` part. There can also be `error` parts which appear when the template couldn't be parsed (which is used to suppress "unrecognised attribute" errors). The actually supplied attributes are compared with the union of the used attributes, and errors are reported for unused ones. ## Implementation approach - ad-hoc macro applications ### 1. `#[derive(Deftly)]` feature for saving struct definitions Also implemented in `derive.rs::derive_deftly`. When applied to (e.g.) `pub struct StructName`, with `#[derive_deftly_adhoc]` specified generates this ```rust,ignore macro_rules! derive_deftly_driver_StructName { { { $($template:tt)* } { ($orig_dollar:tt) $(future:tt)* } $($dpassthrough:tt)* } => { derive_deftly_engine!{ { pub struct StructName { /* original struct definition */ } } /* no AOPTIONS since there's no derive() application */ ( ) { $($template)* } $($dpassthrough)* } } } ``` (Again, the extra `{ }` parts after the driver and template include space for future expansion.) In the `pub struct` part every `$` is replaced with `$orig_dollar`, to use the `$` passed in at the invocation site. (This is only relevant if the driver contains `$` somehow, for example in helper attributes.) ### 2. `derive_deftly_adhoc!` function-like proc macro for applying to a template Implemented in `adhoc.rs::derive_deftly_adhoc`. When applied like this ```rust,ignore derive_deftly_adhoc!{ StructName TOPTIONS..: TEMPLATE.. } ``` Expands to ```rust,ignore derive_deftly_driver_StructName! { { TEMPLATE.. } { ($) } ( crate; [TOPTIONS..] /*no template name*/; ) [] [_meta_used *] } ``` The literal `$` is there to work around a limitation in `macro_rules!`, see above. ### 3. Function-like proc macro to do the actual expansion The result of expanding the above is this: ```rust,ignore derive_deftly_engine!{ { pub struct StructName { /* original struct definition */ } } ( ) { TEMPLATE.. } ( crate; [TOPTIONS..] /*no template name*/; ) [] [_meta_used *] } ``` `derive_deftly_engine` parses `pub struct StructName`, and implements a bespoke template expander, whose template syntax resembles the expansion syntax from `macro_rules`. `crate` is just used as the expansion for `${crate}`. (For an ad-hoc template, the local crate is correct.) work/doc/introduction.md0000664000000000000000000000016014763657303012553 0ustar # Moved This documentation has moved to work/doc/reference.md0000664000000000000000000014632714763657303012010 0ustar # **Template syntax (and expansion options) reference** **Table of contents**
(see also the [Index](#t:index)) * [Template syntax overview](#template-syntax-overview) * [Named and positional template arguments to expansions and conditions](#named-and-positional-template-arguments-to-expansions-and-conditions) * [Repetition and nesting](#repetition-and-nesting) * [Expansions](#expansions) * [`$fname`, `$vname`, `$tname` -- names](#fname-vname-tname--names) * [`$fvis`, `$tvis`, `$fdefvis` -- visibility](#fvis-tvis-fdefvis--visibility) * [`$vpat`, `$fpatname` -- pattern matching and value deconstruction](#vpat-fpatname--pattern-matching-and-value-deconstruction) * [`$ftype`, `$vtype`, `$ttype`, `$tdeftype` -- types](#ftype-vtype-ttype-tdeftype--types) * [`$tgens`, `$tgnames`, `$twheres`, `$tdefgens` -- generics](#tgens-tgnames-twheres-tdefgens--generics) * [`${tmeta(...)}` `${vmeta(...)}` `${fmeta(...)}` -- `#[deftly]` attributes](#tmeta-vmeta-fmeta--deftly-attributes) * [`${fattrs ...}` `${vattrs ...}` `${tattrs ...}` -- other attributes](#fattrs--vattrs--tattrs---other-attributes) * [`${paste ...}`, `$<...>` -- identifier pasting](#paste----identifier-pasting) * [`${CASE_CHANGE ...}` -- case changing](#case_change---case-changing) * [`${when CONDITION}` -- filtering out repetitions by a predicate](#when-condition--filtering-out-repetitions-by-a-predicate) * [`${if COND1 { ... } else if COND2 { ... } else { ... }}` -- conditional](#if-cond1----else-if-cond2----else-----conditional) * [`${select1 COND1 { ... } else if COND2 { ... } else { ... }}` -- expect precisely one predicate](#select1-cond1----else-if-cond2----else-----expect-precisely-one-predicate) * [`${for fields { ... }}`, `${for variants { ... }}`, `$( )` -- repetition](#for-fields----for-variants-------repetition) * [`$crate` -- root of template crate](#crate--root-of-template-crate) * [`$tdefkwd` -- keyword introducing the new data structure](#tdefkwd--keyword-introducing-the-new-data-structure) * [`$tdefvariants`, `$vdefbody`, `$fdefine` -- tools for defining types](#tdefvariants-vdefbody-fdefine--tools-for-defining-types) * [`${ignore ..}` -- Expand but then discard](#ignore---expand-but-then-discard) * [`${dbg ..}`, `$dbg_all_keywords` -- Debugging output](#dbg--dbg_all_keywords--debugging-output) * [`${define ...}`, `${defcond ...}` -- user-defined expansions and conditions](#define--defcond---user-defined-expansions-and-conditions) * [`${error "message"}` -- explicitly throw a compile error](#error-message--explicitly-throw-a-compile-error) * [Conditions](#conditions) * [`fvis`, `tvis`, `fdefvis` -- test for public visibility](#fvis-tvis-fdefvis--test-for-public-visibility) * [`fmeta(NAME)`, `vmeta(NAME)`, `tmeta(NAME)` -- `#[deftly]` attributes](#fmetaname-vmetaname-tmetaname--deftly-attributes) * [`is_struct`, `is_enum`, `is_union`](#is_struct-is_enum-is_union) * [`v_is_unit`, `v_is_tuple`, `v_is_named`](#v_is_unit-v_is_tuple-v_is_named) * [`tgens`](#tgens) * [`is_empty(..)`, `approx_equal(ARG1, ARG2)` -- equality comparison (token comparison)](#is_empty-approx_equalarg1-arg2--equality-comparison-token-comparison) * [`false`, `true`, `not(CONDITION)`, `any(COND1,COND2,...)`, `all(COND1,COND2,...)` -- boolean logic](#false-true-notcondition-anycond1cond2-allcond1cond2--boolean-logic) * [`dbg(...)` -- Debug dump of condition value](#dbg--debug-dump-of-condition-value) * [Case changing](#case-changing) * [Expansion options](#expansion-options) * [`expect items`, `expect expr` -- syntax check the expansion](#expect-items-expect-expr--syntax-check-the-expansion) * [`for struct`, `for enum`, `for union` -- Insist on a particular driver kind](#for-struct-for-enum-for-union--insist-on-a-particular-driver-kind) * [`dbg` -- Print the expansion to stderr, for debugging](#dbg--print-the-expansion-to-stderr-for-debugging) * [`beta_deftly` -- Enable unstable template features](#beta_deftly--enable-unstable-template-features) * [Expansion options example](#expansion-options-example) * [Precedence considerations](#precedence-considerations) * [`None`-delimited groups](#none-delimited-groups) * [Structs used in examples](#structs-used-in-examples) * [Keyword index](#keyword-index) * [Expansions index](#expansions-index) * [Conditions index](#conditions-index) **Reference documentation for the actual proc macros** is in the [crate-level docs for derive-deftly](../index.html#macros). [**beta**]: ../doc_changelog/index.html#t:beta [positional argument]: #t:arguments
## Template syntax overview
Within the macro template, expansions (and control structures) are introduced with `$`. They generally refer to properties of the data structure that we're deriving from. We call that data structure the **driver**. In general the syntax is: * `$KEYWORD`: Invoke the expansion of the keyword `KEYWORD`. * `${KEYWORD ARGS...}`: Invoke with parameters. * `$( .... )`: Repetition (abbreviated, automatic, form). (Note: there is no `+` or `*` after the `)`) * `$< .... >`: Identifier pasting (shorthand for [`${paste ...}`](#x:paste)). In all cases, `$KEYWORD` is equivalent to `${KEYWORD}`. You can pass a `$` through by writing `$$`. Many of the expansion keywords start with `f`, `v`, or `s` to indicate the depth of the thing being expanded: * `f...`: Expand something belonging to a particular Field. * `v...`: Expand something belonging to a particular Variant. * `t...`: Expand something applying to the whole Top-level type. In the keyword descriptions below, `X` is used to stand in for one of `f`, `v` or `t`. Defining a new type based on the driver requires more complex and subtle syntax, generated by special-purpose expansions `$Xdef...`. (Here, within this documentation, we often write in `CAPITALS` to indicate meta-meta-syntactic elements, since all of the punctuation is already taken.) Inner attributes (`#![...]` and `//!...`) are not allowed in templates.
### Named and positional template arguments to expansions and conditions
Some expansions and conditions take (possibly optional) named arguments, or multiple positional arguments, whose values are templates: * `${KEYWORD NAME=ARG NAME=ARG ...}` * `${KEYWORD ARG1 ARG2 ...}` * `CONDITION(NAME=ARG, NAME=ARG, ...)` * `CONDITION(ARG1, ARG2, ...)` The acceptable contents vary, but the syntax is always the same. Each `ARG` must be one of: * `IDENTIFIER` * `LITERAL` (eg, `NUMBER` or `"STRING"`) * `$EXPANSION` (including `${KEYWORD...}`, `$<...>`, etc.) * `{ STUFF }`, where `STUFF` is expanded. (The `{ }` are just for delimiting the value, and are discarded).
## Repetition and nesting
The driving data structure can contain multiple variants, which can in turn contain multiple fields; there are also attributes. Correspondingly, sections of the template, indicated by `${for ...}` and `$(...)`, are expanded multiple times. With `${for ...}`, what is iterated over is specified explicitly. When `$( ... )` is used, what is iterated over is automatically inferred from the content: most expansions and conditions imply a "level": what possibly-repeated part of the driver they correspond to. All the expansions directly within `$(...)` must have the same repetition level. With both `${for }` and `$(...)`, if the repetition level is "deeper" than the level of the surrounding template, the surrounding levels are also repeated over, effectively "flattening". For example, expanding `$( $fname )` at the very toplevel, will iterate over all of the field names; if the driver is an enum; it will iterate over all of the fields in each of the variants in turn. structs and unions do not have variants, but derive-deftly treats them as having a single (unnamed) variant. #### Examples For [example enum `Enum`](#structs-used-in-examples): * `$($vname,)`: `UnitVariant, TupleVariant, NamedVariant,` * `$($fname)`: `0 field field_b field_e field_o` * `${for fields { hello }}`: `hello hello hello hello` ## Expansions Each expansion keyword is described in this section. The examples each show the expansions for (elements of) [the same example `Unit`, `Tuple`, `Struct` and `Enum`](#t:example-structs), shown below.
### `$fname`, `$vname`, `$tname` -- names
The name of the field, variant, or toplevel type. This is an the identifier (without any path or generics). For tuple fields, `$fname` is the field number. `$fname` is not suitable for direct use as a local variable name. It might clash with other local variables; and, unlike most other expansions, `$fname` has the hygiene span of the driver field name. Instead, use `$vpat`, `$fpatname`, or `${paste ... $fname ...}` (`$<... $fname ...>`). #### Examples * `$fname`: `0`, `field`, `field_b` * `$vname`: `UnitVariant` * `$tname`: `Tuple`, `Struct`, `Enum`
### `$fvis`, `$tvis`, `$fdefvis` -- visibility
The visibility of the field, or toplevel type. Expands to `pub`, `pub(crate)`, etc. Expands to nothing for private types or fields. This looks only at the syntax in the driver definition; an item which is `pub` might still not be reachable, for example if it is in a private inner module.
#### Enums and visibility
In Rust, enum variants and fields don't have separate visibility; they inherit visibility from the enum itself. So there is no `$vvis`. For enum fields, `$fvis` expands to the same as `$tvis`. Use `$fvis` for the effective visibility of a field, eg when defining a derived method. `$fdefvis` is precisely what was written in the driver field definition, so always expands to nothing for enum fields - even though those might be public. Use `$fdefvis` when defining a new enum. #### Examples * `$tvis` for `Unit`: `pub` * `$tvis` for `Enum`: `pub` * `$tvis` for others: nothing * `$fvis` for `field` in `Struct`: `pub` * `$fvis` for `field_b` in `Struct`: `pub(crate)` * `$fvis` for fields in `Enum`: `pub` * `$fvis` for others: nothing * `$fdefvis` for `field` in `Struct`: `pub` * `$fdefvis` for `field_b` in `Struct`: `pub(crate)` * `$fdefvis` for fields in `Enum`: nothing * `$fdefvis` for others: nothing
### `$vpat`, `$fpatname` -- pattern matching and value deconstruction
`$vpat` expands to a pattern suitable for matching a value of the top-level type. It expands to `TYPE { FIELD: f_FNAME, ... }`, where `TYPE` names the top-level type or enum variant. (`TYPE` doesn't have generics, since those are not allowed in patterns.) Each field is bound to a local variant `f_FNAME`, where `FNAME` is the actual field name (or tuple field number). `$fpatname` expands to `f_FNAME` for the current field. #### `$vpat` named arguments * `self`: top level type path. Default is `$tname`. Must expand to a syntactically valid type path, without generics. * `vname`: variant name. Default is `$vname`. Not expanded for structs. * `fprefix`: prefix to use for the local bindings. Useful if you need to bind multiple values at once. (Then, reference the bindings with `$`; `$fpatname` doesn't take a `fprefix` argument.) Default is `f_`. These use derive-deftly's usual [syntax for named arguments](#named-and-positional-template-arguments-to-expansions-and-conditions). #### Examples * `$vpat` for structs: `Unit { }`, `Tuple { 0: f_0, }` * `$vpat` for enum variant: `Enum::NamedVariant { field: f_field, ... }` * `$fpatname`: `f_0`, `f_field` * `${vpat self=$<$tname Reference> vname=$ fprefix=other_}`: `EnumReference::RefNamedVariant { field: other_field, ... }`
### `$ftype`, `$vtype`, `$ttype`, `$tdeftype` -- types
The type of the field, variant, or the toplevel type. `$ftype`, `$vtype` and `$ttype` are suitable for referencing the type in any context (for example, when defining the type of a binding, or as a type parameter for a generic type). These contains all necessary generics (as names, without any bounds etc., but within `::<...>`). `$vtype` includes both the top-level enum type, and the variant. To construct a value, prefer `$vtype` rather than `$ttype`, since `$vtype` works with enums too. `$tdeftype` is the driver type in a form suitable for defining a new type with a derived name (eg, using pasting). Contains all the necessary generics, with bounds, within `<...>` but without an introducing `::`. The toplevel type expansions, `$ttype` and `$tdeftype`, don't contain a path prefix, even when a driver type argument to `derive_deftly_adhoc!` has a path prefix. `$vtype` (and `$ttype` and `$tdeftype`) are not suitable for matching. Use `$vpat` for that. #### `$vtype` named arguments * `self`: top level type. Default is `$ttype`. Must expand to a syntactically valid type. * `vname`: variant name. Default is `$vname`. Not expanded for structs. These can be specified using pasting `$<...>` to name related (derived) types and variants. They use derive-deftly's usual [syntax for named arguments](#named-and-positional-template-arguments-to-expansions-and-conditions). #### Examples * `$ftype`: `« std::iter::Once:: »`, `« Option:: »` * `$vtype` for struct: `Tuple::<'a, 'l, T, C>` * `$vtype` for enum variant: `Enum::TupleVariant::<'a, 'l, T, C>` * `$ttype`: `Enum::<'a, 'l, T, C>` * `$tdeftype`: `Enum<'a, 'l: 'a, T: Display = usize, const C: usize = 1>` * `${vtype self=$<$ttype Reference> vname=$}` for enum variant: `EnumReference::RefTupleVariant::<'a, 'l, T, C>`
### `$tgens`, `$tgnames`, `$twheres`, `$tdefgens` -- generics
Generic parameters and bounds, from the toplevel type, in various forms. * **`$tgens`**: The generic arguments, with bounds (and the types of const generics) but without defaults. Suitable for use when starting an `impl`. * **`$tgnames`**: The generic argument names, without bounds. Suitable for use in a field type or in the body of an impl. * **`$twheres`**: The where clauses, as written in the toplevel type definition. * **`$tdefgens`**: The generic arguments, with bounds, *with* defaults, as written in the toplevel type definition, suitable for defining a derived type. If not empty, each of these will always have a trailing comma. Bounds appear in `$tgens`/`$tdefgens` or `$twheres`, according to where they appear in the toplevel type, so for full support of generic types the template must expand both. #### Examples * `$tgens`: `'a, 'l: 'a, T: Display, const C: usize,` * `$tgnames`: `'a, 'l, T, C,` * `$twheres`: `T: 'l, T: TryInto,` * `$tdefgens`: `'a, 'l: 'a, T: Display = usize, const C: usize = 1,`
### `${tmeta(...)}` `${vmeta(...)}` `${fmeta(...)}` -- `#[deftly]` attributes
Accesses macro parameters passed via `#[deftly(...)]` attributes. * **`${Xmeta(NAME)}`**: Looks for `#[deftly(NAME="VALUE")]`, and expands to `VALUE`. `"VALUE"` must be be a string literal, which is parsed as a piece of Rust code, and then expanded. Normally, `aa ..` must be given, to specify how `VALUE` should be parsed; within `$(paste ..}`, `as str` is the default. * **`${Xmeta(SUB(NAME))}`**: Looks for `#[deftly(SUB(NAME="VALUE"))]`. The `#[deftly()]` is parsed as a set of nested, comma-separated, lists. So this would find `NAME` in `#[deftly(SUB1,SUB(N1,NAME="VALUE",N2),SUB2)]`. The label can be arbitrarily deep, e.g.: `${Xmeta(L1(L2(L3(ATTR))))}`. * **`${Xmeta(...) as SYNTYPE}`**: Treats the value as a `SYNTYPE`. `SYNTYPE`s available are: * **`str`**: Expands to a string literal with the same contents as the string provided for `VALUE`. Ie, the attribute's string value is *not* parsed. This is the default within pasting and case changing, if no `as` was specified. Within pasting and case changing, the provided string becomes part of the pasted identifier (and so must consist of legal identifier characters). * **`ty`**: `VALUE` is parsed as a type, possibly with generics etc. (`syn::Type`). When expanded, generic arguments have any missing `::` inserted, so that the expansion is suitable for use in any context, (such as invoking an inherent or trait method). * **`path`**: `VALUE` is parsed as a path, possibly with generics etc. (`syn::Path`). Like `as ty` but non-path types are forbidden. Rust uses the same path syntax for types and modules, so this is suitable for accepting a module path, too. * **`expr`**: `VALUE` is parsed as an expression. When expanded, it is surrounded with `( )` to ensure correct precedence. * **`ident`**: `VALUE` is parsed as an identifier (or keyword). (Within pasting, prefer `as str`, the default; `as ident` rejects initial digits, and the empty string.) * **`items`**: `VALUE` is parsed as zero or more Rust Items (`syn::Item`). Note that the driver must pass the items' source code in `"..."`. * **`token_stream`**: `VALUE` is parsed as an arbtitrary sequence of tokens (`TokenStream`). When using this option, be careful about operator precedence: see [Precedence considerations](#precedence-considerations). * **`${Xmeta(...) .. , default DEFAULT}`** ([**beta**]): If there is no `VALUE` expands the [positional argument] `DEFAULT` instead. NB: in this case the expansions of `DEFAULT` is used as is: *not* affected by any `as ..` clause; *not* surrounded with additional `( )` (for `as expr`), nor any additional [`None`-delimited group](#t:none-delimiters). When expanding `${Xmeta}`, it is an error if the value was not specified in the driver, and also an error if multiple values were specified. For a struct, both `$tmeta` and `$vmeta` look in the top-level attributes. This allows a template to have uniform handling of attributes which should affect how a set of fields should be processed. Within `${Xmeta ..}`, options (`as` and `default`) are each introduced with a keyword, and separated by commas. #### Attribute namespacing `derive-deftly` does not impose any namespacing within `#[deftly]`: all templates see the same `deftly` meta attributes. To avoid clashes, macros intended for general use should look for attributes within a namespace for that template. The usual convention is to accept attributes scoped within the snake-cased name of the template, as demonstrated [in the introduction](https://diziet.pages.torproject.net/rust-derive-deftly/latest/guide/constructor-attrs.html#meta-attr-scope). #### Unrecognised/unused `#[deftly(...)]` attributes Every `#[deftly(...)]` attribute on the input data structure must correspond to a `${Xmeta...}` expansion (or `fmeta(...)` boolean test, as applicable) in the template(s) applied to that driver. The `Xmeta` reference must have been actually expanded (or tested), so parts of the template that weren't expanded don't count. These checks are disabled by `#[derive_deftly_adhoc]`. #### Examples * `${tmeta(simple) as ty}`: `« String »` * `${tmeta(missing) as ty, default String}`: `String` * `${tmeta(simple) as path}`: `« String »` * `${tmeta(simple) as str}`: `"String"` * `${tmeta(simple) as token_stream}`: `String` * `${tmeta(gentype) as ty}`: `« Vec:: »` * `${tmeta(gentype) as str}`: `"Vec"` * `${tmeta(gentype) as token_stream}`: `Vec` * `${vmeta(value) as ident}`: `unit_toplevel`, `enum_variant` * `${fmeta(nested(inner)) as expr}` for `field` in `Struct`: `(42)` * `${vmeta(items) as items}` for `TupleVariant`: `type T = i32; const K: T = 7;` * `${fmeta(nested)}`: rejected, ``expected a leaf node, found a list with sub-attributes`` #### Examples involving pasting * `$`: `SmallString` * `$`: `SmallString` * `$`: `« SmallString »` * `$`: `« SmallVec:: »` * `$<$ttype ${tmeta(simple) as str}>`: `UnitString::` * `$<$ttype ${tmeta(simple) as ty}>`: error, ``multiple nontrivial entries``
### `${fattrs ...}` `${vattrs ...}` `${tattrs ...}` -- other attributes
Expands to attributes, including non-`#[deftly()]` ones. The attributes can be filtered: * **`$Xattrs`**: All the attributes except `#[deftly]` and `#[derive_deftly]` * **`${Xattrs A1, A2, ...}`**, or **`${Xattrs = A, A2, ...}`**: Attributes `#[A1...]` and `#[A2...]` only. * **`${Xattrs ! A1, A2, ...}`**: All attributes *except* those. With `${Xattrs}`, unlike `${Xmeta}`, * The expansion is the whole of each attribute, including the `#[...]`; * All attributes are included. * But `#[deftly(...)]` `#[derive_deftly(...)]` and `#[derive_deftly_adhoc(...)]` are *excluded* by default, because typically they would be rejected by the compiler: the expanded output is (perhaps) no longer within `#[derive(Deftly)]`, so those attributes might be unrecognised there. * The attributes can be filtered by toplevel attribute name, but not deeply manipulated. * `$vattrs` does not, for a non-enum, include the top-level attributes . Note that derive macros, only see attributes that come *after* the `#[derive(...)]` that invoked them. So derive-deftly templates only see attributes that come *after* the `#[derive(..., Deftly, ...)]`. #### Examples ##### For `Unit` * `${tattrs}`: ``#[derive(Clone)]`` * `${tattrs ! deftly}`: ``#[derive(Clone)]`` * `${tattrs missing}`: nothing * `${tattrs derive}`: ``#[derive(Clone)]`` * `${vattrs deftly}`: nothing ##### For `Tuple` * `${tattrs}`: ``#[doc=" Title for `Tuple`"] #[repr(C)]`` * `${tattrs repr}`: ``#[repr(C)]`` * `${tattrs repr, deftly}`: ``#[deftly(unused)] #[repr(C)]`` * `${tattrs ! derive, doc}`: ``#[deftly(unused)] #[repr(C)] #[derive_deftly(SomeOtherTemplate)]`` ##### For `Enum` * `${vattrs deftly}` for `UnitVariant`: `#[deftly(value="enum_variant")]`
### `${paste ...}`, `$<...>` -- identifier pasting
Expands the contents and pastes it together into a single identifier. The contents may only contain identifer fragments, strings (`"..."`), and (certain) expansions. Supported expansions are `$ftype`, `$ttype`, `$tdeftype`, `$Xname`, `${Xmeta as str / ty / path / ident}`, `$<...>`, `${paste ...}`, `${CASE_CHANGE ...}`, `$tdefkwd`, as well as conditionals and repetitions. The contents can contain at most one occurrence of a more complex type expansion `${Xtype}` (or `${Xmeta as ty)`), which must refer to a path (perhaps with generics, and/or surrounding `( )`). Then the pasting will be applied to the final path element identifier, and the surroundings reproduced unaltered. Iff necessary, the result will be a raw identifier. #### Examples * `$` for `TupleVariant`: `« std::iter::ZingyOnceBuilder:: »` * `${paste x_ $fname}` for tuple: `x_0` * `${paste $fname _x}` for tuple: error, ``constructed identifier "0_x" is invalid`` ### `${CASE_CHANGE ...}` -- case changing Expands the content, and changes its case (eg. uppercase to lowercase, etc. See [Case changing](#case-changing). `CASE_CHANGE` is one of the values listed there.
### `${when CONDITION}` -- filtering out repetitions by a predicate
Allowed only within repetitions, and only at the toplevel of the repetition, before other content. Skips this repetition if the `CONDITION` is not true. #### Example * `$( ${when vmeta(value)} ${vmeta(value) as str} )` for `Enum`: `"enum_variant"`
### `${if COND1 { ... } else if COND2 { ... } else { ... }}` -- conditional
Conditionals. The else clause is, of course, optional. The `else if` between arms is also optional, but `else` in the fallback clause is mandatory. So you can write `${if COND1 { ... } COND2 { ... } else { ... }`. #### Examples * `${if is_enum { E } is_struct { S }}` for `Enum`: `E` * `${if is_enum { E } is_struct { S }}` for others: `S` * `$( ${if v_is_named { N } v_is_tuple { T }} )` for `Enum`: `T N` * `$( ${if v_is_named { N } v_is_tuple { T } else { X }} )` for `Enum`: `X T N` * `${if v_is_unit { U } tmeta(gentype) { GT }}` for `Unit`: `U`
### `${select1 COND1 { ... } else if COND2 { ... } else { ... }}` -- expect precisely one predicate
Conditionals which insist on exactly one of the tests being true. Syntax is identical to that of `${if }`. *All* of the `COND` are always evaluated. Exactly one of them must be true; or, none of them, but only if an `else` is supplied - otherwise it is an error. #### Examples * `${select1 is_enum { E } is_struct { S }}`: `E`, `S` * `${select1 v_is_named { N } v_is_tuple { T }}` for `Enum`: rejected, ``no conditions matched, and no else clause`` * `$( ${select1 v_is_named { N } v_is_tuple { T } else { X }} )` for `Enum`: `X T N` * `${select1 v_is_unit { U } tmeta(gentype) { GT }}` for `Unit`: rejected, ``multiple conditions matched``
### `${for fields { ... }}`, `${for variants { ... }}`, `$( )` -- repetition
`${for ...}` expands the contents once per field, or once per variant. `$( ... )` expands the input with an appropriate number of iterations - see [Repetition and nesting](#repetition-and-nesting).
### `$crate` -- root of template crate
`$crate` always refers to the root of the crate defining the template. Within an `export`ed template, being expanded in another crate, it refers to the crate containing the template definition. In templates being used locally, it refers to the current crate, ie simply `crate`. This is similar to the `$crate` builtin expansion in `macro_rules!`.
### `$tdefkwd` -- keyword introducing the new data structure
Expands to `struct`, `enum`, or `union`.
### `$tdefvariants`, `$vdefbody`, `$fdefine` -- tools for defining types
These, used together, allow the template to expand to a new definition, mirroring the driver type in form. **`${tdefvariants VARIANTS..}`** expands to `{ VARIANTS.. }` for an enum, or just `VARIANTS..` otherwise. Usually, it would contain a `$( )` repeating over the variants, expanding `$vdefbody` for each one. **`${vdefbody VNAME FIELDS..}`** expands to the definition of a variant, with a appropriate delimiters. `VNAME` is in the standard syntax for a positional argument, and `FIELDS..` is the rest of the content. Usually, `FIELDS..` would contain a `$( )` repeating over the fields, using `$fdefine` to introduce each one. Specifically: ```rust,dd-directly # let _ = r##" ${vdefbody VNAME FIELDS} for unit FIELDS; [*] ie ; ${vdefbody VNAME FIELDS} for tuple ( FIELDS ); ${vdefbody VNAME FIELDS} for braced struct { FIELDS } ${vdefbody VNAME FIELDS} for unit variant VNAME FIELDS, [*] ie VNAME, ${vdefbody VNAME FIELDS} for tuple variant VNAME ( FIELDS ), ${vdefbody VNAME FIELDS} for braced variant VNAME { FIELDS }, # "##; ``` **`${fdefine FNAME}`** expands to `FNAME:` in the context of named fields (a "struct" or "struct variant"), or nothing otherwise. `FNAME` is in the standard syntax for a positional argument, `[*]`: In the unit and unit variant cases, `FIELDS` ought to expand to nothing; otherwise, the expansion of `$vdefbody` will probably be syntactically invalid in context. #### Example ```rust,dd-directly # let _ = r##" $tvis $tdefkwd $<$tname Copy><$tdefgens> ${tdefvariants $( ${vdefbody $<$vname Copy> $( $fdefvis ${fdefine $<$fname _copy>} $ftype, ) } ) } # "##; ``` Expands to (when applied to `Tuple` and `Enum`): ```rust,dd-directly # let _ = r##" struct TupleCopy<'a, 'l: 'a, T: Display = usize, const C: usize = 1,>( &'a &'l T, ); pub enum EnumCopy<'a, 'l: 'a, T: Display = usize, const C: usize = 1,> { UnitVariantCopy, TupleVariantCopy(std::iter::Once::,), NamedVariantCopy { field_copy: &'l &'a T, ... }, } # "##; ```
### `${ignore ..}` -- Expand but then discard
`${ignore CONTENT}` expands `CONTENT`, and then discards it. The `${ignore ..}` therefore expands to nothing. All side-effects of `CONTENT` *do* occur. So: if expanding `CONTENT` causes an error, `${ignore }` *does* report that error; the content of `${ignore }` *can* affect the repetition scope of its surroundings. `${ignore }` is permitted in `${paste }` and case changing.
### `${dbg ..}`, `$dbg_all_keywords` -- Debugging output
`${dbg { CONTENT }}` expands to the expansion of `CONTENT`, but it also prints the expansion to the compiler stderr. `${dbg "NOTE" { CONTENT }}` adds the note `"NOTE"` to the heading of the expansion dump, for identification purposes. `$dbg_all_keywords` dumps expansions of all keywords: It prints a listing of all the available expansion keywords, and conditions, along with their expansions and values. When invoked at the toplevel, it prints a report for each variant and field. (The output goes to the compiler's stderr; the actual expansion is empty.) This can be helpful to see which expansion keywords might be useful for a particular task. (Before making a final selection of keyword you probably want to refer to this reference manual.) You will not want to leave these options in production code, as they make builds noisy. See also the [`dbg` expansion option](#dbg--print-the-expansion-to-stderr-for-debugging), and the [`dbg` condition](#dbg--debug-dump-of-condition-value). #### Example ```rust # use derive_deftly::{Deftly, derive_deftly_adhoc}; #[derive(Deftly)] #[derive_deftly_adhoc] enum Enum { Unit, Tuple(usize), Struct { field: String }, } derive_deftly_adhoc! { Enum: $dbg_all_keywords // ... rest of the template you're developing ... } ```
### `${define ...}`, `${defcond ...}` -- user-defined expansions and conditions
`${define NAME BODY}` defines a reuseable piece of template. Afterwards, `$NAME` (and `${NAME}`) expand `BODY`. `${defcond NAME CONDITION}` defines a reuseable condition. Afterwards, the name `NAME` can be used as a condition - evaluating `CONDITION`. `NAME` is an identifier. It may not start with a lowercase letter or underscore: those expansion names are reserved for derive-deftly's built-in functionality. `BODY` is in the [standard syntax for positional arguments](#named-and-positional-template-arguments-to-expansions-and-conditions). When generating Rust code, be careful about operator precedence: see [Precedence considerations](#precedence-considerations). `CONDITION` is in the standard syntax for a condition. `NAME` is visible after its definition in the same template or group, including in inner templates and groups. Definitions may be re-defined, in the same scope, or inner scopes. Scope is dynamic, both for derive-deftly built-ins and user definitions: `BODY` and `CONDITION` are captured without expansion/evaluation at the site of `$define`/`$defcond`, and the contents expanded/evaluated each time according to the values and definitions prevailing in the dynamic context where `NAME` is used. (Therefore, you can `$define`/`$defcond` an identifier at a point where its contents are not in scope, and expand it later when they are.) `${NAME}` may only be used inside pasting and case changing if `BODY` was precisely an invocation of `${paste }` or `$<...>`. You can define an expansion and a condition with the same name; they won't interfere. #### Examples * `${define VN $vname} ${for variants { $VN }}`: `UnitVariant TupleVariant NamedVariant` * `${define FN $<$fname _>} $<${for fields { "F" $FN }}>`: `F0_`, `Ffield_Ffield_b_` ##### Example including a condition ```rust,dd-directly # let _ = r##" ${define T_FIELDS ${paste $tname Fields}} // Note that fvis is not in scope here; that's okay, // but we can only _use_ F_ENABLE when fvis _is_ in scope. ${defcond F_ENABLE all(fvis, v_is_named)} $tvis struct $T_FIELDS { $( ${when F_ENABLE} $fvis $fname: bool, ) } $tvis const ${shouty_snake_case ALL_ $T_FIELDS}: $T_FIELDS = { $( ${when F_ENABLE} $fname: true, ) }; # "##; ``` Expands to (for `Unit`, `Tuple` and `Struct`): ```rust,dd-directly # let _ = r##" pub struct UnitFields {} pub const ALL_UNIT_FIELDS: UnitFields = {}; struct TupleFields {} const ALL_TUPLE_FIELDS: TupleFields = {}; struct StructFields { pub field: bool, } const ALL_STRUCT_FIELDS: StructFields = { field: true, }; # "##; ```
### `${error "message"}` -- explicitly throw a compile error
Generates a compilation error, if expanded. This can be used anywhere a derive-deftly expansion is allowed; (unlike std's `compile_error!`, which, like any Rust macro, is permitted only in certain syntactic contexts). ## Conditions Conditions all start with a `KEYWORD`. They are found within `${if }`, `${when }`, and `${select1 }`.
### `fvis`, `tvis`, `fdefvis` -- test for public visibility
True iff the field, or the whole toplevel type, is `pub`. See [`$fvis`, `$tvis` and `$fdefvis`](#fvis-tvis-fdefvis--visibility) for details of the semantics (especially for enums), and the difference between `$fvis` and `$fdefvis`. Within-crate visibility, e.g. `pub(crate)`, is treated as "not visible" for the purposes of `fvis` and `tvis` (although the `$fvis` and `$tvis` expansions will handle those faithfully). #### Examples * `tvis`: true for `Unit`, and `Enum` * `fvis`: true for `field` in `Struct`, and fields in `Enum` * `fdefvis`: true for `field` in `Struct` And in each case, false for all others. (Refer to the [example structs](#structs-used-in-examples), below.)
### `fmeta(NAME)`, `vmeta(NAME)`, `tmeta(NAME)` -- `#[deftly]` attributes
Looks for `#[deftly(NAME)]`. True iff there was such an attribute. The condition is true if there is at least one matching entry, and (unlike `${Xmeta}`) the corresponding driver attribute does not need to be a `=LIT`. So `Xmeta(SUB(NAME))` is true if the driver has `#[deftly(SUB(NAME(INNER=...)))]` or `#[deftly(SUB(NAME))]` or `#[deftly(SUB(NAME=LIT))]` or even `#[deftly(SUB(NAME()))]`. `Xmeta(SUB(NAME))` works, just as with the `${Xmeta ...}` expansion. See [`${Xmeta ...}`](#tmeta-vmeta-fmeta--deftly-attributes) for information about namespacing and handling of unused attributes. #### Examples * `tmeta(unused)`: true for `Tuple` * `tmeta(gentype)`: true for `Unit` * `vmeta(value)`: true for `Unit`, and `Enum::UnitVariant` * `fmeta(nested)`: true for `field` in `Struct`
### `is_struct`, `is_enum`, `is_union`
The driver data structure is a struct, enum, or union, respectively. Prefer to avoid these explicit tests, when writing a template to work with either structs or enums. Instead, use `match` and `$vpat` for deconstructing values, and `$vtype` for constructing them. Use `$tdefvariants` when defining a derived type.
### `v_is_unit`, `v_is_tuple`, `v_is_named`
Whether and what kind of fields there are. Prefer to avoid these explicit tests, when writing a template to work with any shape of structure. Instead, match using Rust's universal `Typename { }` syntax, possibly via `$vpat` and `$fpatname`, or via `$vtype`. The `Typename { }` syntax can be used for matching and constructing all kinds of structures, including units and tuples. Use `$vdefbody` and `$fdefine` when defining a derived type. #### Examples * `v_is_unit`: true for `struct Unit;`, `SimpleUnit`, and `Enum::UnitVariant;` * `v_is_tuple`: true for `struct Tuple(...);`, and `Enum::TupleVariant(...);` * `v_is_named`: true for `struct Struct {...}`, and `Enum::NamedVariant {...}`
### `tgens`
Whether the top-level type has generics. #### Examples * `tgens`: true for `Unit`, `Tuple`, `Struct`, `Enum`
### `is_empty(..)`, `approx_equal(ARG1, ARG2)` -- equality comparison (token comparison)
`is_empty` expands the content, and is true if the expansion produced no tokens. `approx_equal` expands the two `ARGS`s (as series of tokens) and compares them for (a kind of) equality. Span is disregarded, so two identifiers that would refer to different types or values, but which have the same name, would count as equal. Spacing is disregarded, even between punctuation characters. For example, `approx_equal` regards `<<` as equal to `< <`. This means expansions might count as equal even if the Rust compiler would accept one and reject the other; and, expansions might count as equal even if macros could tell the difference. Also, `None`-delimited groups, which are used by macros (including derive-deftly and `macro_rules!`) for preventing precedence surprises, are flattened - the wrapping by an invisible group is ignored. This means that two expressions with different values, due to different evaluation orders, can compare equal! If both inputs are valid Rust types, they will only compare equal if they are syntactically the same. (Note that different ways of writing the same type are treated as different: for example, `Vec` is not equal to `Vec` and `std::os::raw::c_char` is not equal to `std::ffi::c_char`.) Literals are generally compared by value: * Integer literals are compared by value, ignoring any type suffixes. (Both values `>u64` is unsupported.) * String, byte and character literals are compared by value. `c"..."` literals are unsupported. (Suffixes are unsupported.). * Floating point literals are compared *textually*, not by value; so are considered equal only if written identically. * Negative literals are compared as two tokens, `-` and a nonnegative literal. * Comparison of other literals is unsupported. Raw identifiers are considered unequal to non-raw identifiers, even if the designated identifier is the same. The `ARG`s are in derive-deftly's usual [syntax for positional arguments](#named-and-positional-template-arguments-to-expansions-and-conditions).
### `false`, `true`, `not(CONDITION)`, `any(COND1,COND2,...)`, `all(COND1,COND2,...)` -- boolean logic
`any()` and `all()` short circuit: as soon as they have established they answer, they don't test the remaining conditions. (This affects error handling, and meta attribute use checking.)
### `dbg(...)` -- Debug dump of condition value
`dbg(CONDITION)` evaluates `CONDITION`, but it also prints the boolean value to the compiler stderr. `dbg("NOTE", CONDITION}` adds the note `"NOTE"` to the debug message for identification purposes. You will not want to leave this option in production code, as it makes builds noisy. See also the [`${dbg ..}` expansion](#dbg--dbg_all_keywords--debugging-output) and the [`dbg` expansion option](#dbg--print-the-expansion-to-stderr-for-debugging).
## Case changing
`${CASE_CHANGE ...}` (where `CASE_CHANGE` is one of the keywords in the table, below) makes an identifier with a different case to the input which produces it. This is useful to make identifiers with the natural spelling for their kind, out of identifiers originally for something else. If the content's expansion is a path, only the final segment is changed. The content must be valid within `${paste }`, and is treated the same way. `${CASE_CHANGE }` may appear within pasting and vice versa. This table shows the supported case styles. Note that changing the case can add and remove underscores. The precise details are as for [`heck`], which is used to implement the actual case changing. | `CASE_CHANGE` | `CASE_CHANGE` aliases | Name in [`heck`] | Example of results | |----------------------|----------------------------------|-----------------------------------|-----------------------| | `pascal_case` | `upper_camel_case` | `UpperCamelCase` | `PascalCase` | | `snake_case` | | `SnakeCase` | `snake_case` | | `shouty_snake_case` | | `ShoutySnakeCase` | `SHOUTY_SNAKE_CASE` | | `lower_camel_case` | | `LowerCamelCase` | `lowerCamelCase` | #### Examples * `${shouty_snake_case $ttype}`: `ENUM::<'a, 'l, T, C>` * `${pascal_case $fname}`: `Field`, `FieldB` * `${pascal_case x_ $fname _y}`: `XFieldBY` * `$`: `x_fieldB_y` * `${lower_camel_case $fname}` for tuple: error, ``constructed identifier "0" is invalid`` ## Expansion options You can pass options, which will be applied to each relevant template expansion: ```rust,ignore // Expand TEMPLATE for DataStructureType, with OPTIONS derive_deftly_adhoc! { DataStructureType OPTIONS,... : TEMPLATE } // Define a template Template which always expands with OPTIONS define_derive_deftly! { Template OPTIONS,...: TEMPLATE } // Expand Template for DataStructureType, with OPTIONS #[derive(Deftly)] #[derive_deftly(Template[OPTIONS,...])] struct DataStructureType { # } ``` Multiple options, perhaps specified in different places, may apply to a single expansion. Even multiple occurrences of the same option are fine, so long as they don't contradict each other. The following expansion options are recognised:
### `expect items`, `expect expr` -- syntax check the expansion
Syntax checks the expansion, checking that it can be parsed as items, or as an expression. If not, it is an error. Also, then, an attempt is made to produce compiler error message(s) pointing to the syntax error *in a copy of the template expansion*, as well as reporting the error at the part of the template or driver which generated that part of the expansiuon. This is useful for debugging. Note that a template defined with `define_derive_adhoc!` must always expand to items, anyway, because Rust insists that a `#[derive]` expands to items.
### `for struct`, `for enum`, `for union` -- Insist on a particular driver kind
Checks the driver data structure kind against the `for` option value. If it doesn't match, it is an error. This is useful to produce good error messages: Normally, derive-deftly does not explicitly check the driver kind, and simply makes it available to the template via expansion variables. But, often, a template is written only with a particular driver kind in mind, and otherwise produces syntactically invalid output leading to confusing compiler errors. This option is only allowed in a template, not in a driver's `#[derive_deftly]` attribute.
### `dbg` -- Print the expansion to stderr, for debugging
Prints the template's expansion to stderr, during compilation, for debugging purposes. You will not want to leave this option in production code, as it makes builds noisy. See also the [`${dbg ..}` expansion](#dbg--dbg_all_keywords--debugging-output), the [`dbg` condition](#dbg--debug-dump-of-condition-value), the [`$dbg_all_keywords` expansion](#dbg--dbg_all_keywords--debugging-output).
### `beta_deftly` -- Enable unstable template features
Enables [beta template features](../doc_changelog/index.html#t:beta). This option is only allowed in a template, not in a driver's `#[derive_deftly]` attribute. ### Expansion options example ``` # use derive_deftly::{define_derive_deftly, Deftly}; define_derive_deftly! { Nothing for struct, expect items: } #[derive(Deftly)] #[derive_deftly(Nothing[expect items, dbg])] struct Unit; ``` This defines a reuseable template `Nothing` which can be applied only to structs, and whose output is syntax checked as item(s). (The template's actual expansion is empty, so it does indeed expand to zero items.) Then it applies that to template to `struct Unit`, restating the requirement that the expansion should be item(s). and dumping the expansion to stderr during compilation. ## Precedence considerations When using `${Xmeta as token_stream}`, and user-defined expansions (`${define ...}`) it can be necessary to add `{ }` or `( )` to avoid surprising expansions due to operator precedence. ``` # use derive_deftly::{Deftly, derive_deftly_adhoc}; #[derive(Deftly)] #[derive_deftly_adhoc] struct S(u32, u32); let product = derive_deftly_adhoc!( S: ${define F_PLUS_TWO {$fname + 2}} ${for fields { $F_PLUS_TWO * }} 1 // (0 + 2) * (1 + 2) * 1 = 2 * 3 * 1 = 6 // but this is // 0 + 2 * 1 + 2 * 1 = 0 + (2 * 1) + (2 * 1) = 4 ); assert_eq!(product, 4); ``` Rust demands that *types* are expressed unambiguously, so precedence problems, and lack of `( )` (or `< >`), are detected by the compiler, and rejected.
### `None`-delimited groups
In theory Rust has a feature that would help with this: syntactic groups [can be surrounded by invisible delimiters](https://doc.rust-lang.org/proc_macro/enum.Delimiter.html#variant.None). However, as of May 2024 this feature does not work (and has never worked). See [rust-lang/rust#67062](https://github.com/rust-lang/rust/issues/67062). Nevertheless, derive-deftly surrounds certain expansions with None-delimited groups. These are shown in the example outputs, in this reference, surrounded by guillemets `« »`. This is done for * `$ftype` * `$Xmeta as ty`
## Structs used in examples
The example expansions in the syntax reference are those generated for the following driver types: ```rust,dd-directly # let _ = r##" # # use std::fmt::Display; # use std::convert::TryInto; # #[derive(Deftly)] #[derive(Clone)] struct SimpleUnit; #[derive(Deftly)] #[derive(Clone)] #[deftly(simple="String", gentype="Vec")] #[deftly(value="unit_toplevel")] pub struct Unit; #[derive(Deftly, Clone)] /// Title for `Tuple` #[deftly(unused)] #[repr(C)] #[derive_deftly(SomeOtherTemplate)] struct Tuple<'a, 'l: 'a, T: Display = usize, const C: usize = 1>( &'a &'l T, ); #[derive(Deftly)] struct Struct<'a, 'l: 'a, T: Display = usize, const C: usize = 1> where T: 'l, T: TryInto { #[deftly(nested(inner = "42"))] pub field: &'l &'a T, pub(crate) field_b: String, } #[derive(Deftly)] pub enum Enum<'a, 'l: 'a, T: Display = usize, const C: usize = 1> where T: 'l, T: TryInto { #[deftly(value="enum_variant")] UnitVariant, #[deftly(items="type T = i32; const K: T = 7;")] TupleVariant(std::iter::Once::), NamedVariant { field: &'l &'a T, field_b: String, field_e: >::Error, field_o: Option, }, } # "##; ```
## Keyword index
### Expansions index * **$c…**: [`crate`](#x:crate) * **$d…**: [`dbg`](#x:dbg), [`dbg_all_keywords`](#x:dbg_all_keywords), [`defcond`](#x:defcond), [`define`](#x:define) * **$e…**: [`error`](#x:error) * **$f…**: [`fattrs`](#x:fattrs), [`fdefine`](#x:fdefine), [`fdefvis`](#x:fdefvis), [`fmeta`](#x:fmeta), [`fname`](#x:fname), [`for`](#x:for), [`fpatname`](#x:fpatname), [`ftype`](#x:ftype), [`fvis`](#x:fvis) * **$i…**: [`if`](#x:if), [`ignore`](#x:ignore) * **$l…**: [`lower_camel_case`](#x:lower_camel_case) * **$p…**: [`pascal_case`](#x:pascal_case), [`paste`](#x:paste) * **$s…**: [`select1`](#x:select1), [`shouty_snake_case`](#x:shouty_snake_case), [`snake_case`](#x:snake_case) * **$t…**: [`tattrs`](#x:tattrs), [`tdefgens`](#x:tdefgens), [`tdefkwd`](#x:tdefkwd), [`tdeftype`](#x:tdeftype), [`tdefvariants`](#x:tdefvariants), [`tgens`](#x:tgens), [`tgnames`](#x:tgnames), [`tmeta`](#x:tmeta), [`tname`](#x:tname), [`ttype`](#x:ttype), [`tvis`](#x:tvis), [`twheres`](#x:twheres) * **$u…**: [`upper_camel_case`](#x:upper_camel_case) * **$v…**: [`vattrs`](#x:vattrs), [`vdefbody`](#x:vdefbody), [`vmeta`](#x:vmeta), [`vname`](#x:vname), [`vpat`](#x:vpat), [`vtype`](#x:vtype) * **$w…**: [`when`](#x:when) ### Conditions index * **a…**: [`all`](#c:all), [`any`](#c:any), [`approx_equal`](#c:approx_equal) * **d…**: [`dbg`](#c:dbg) * **f…**: [`false`](#c:false), [`fdefvis`](#c:fdefvis), [`fmeta`](#c:fmeta), [`fvis`](#c:fvis) * **i…**: [`is_empty`](#c:is_empty), [`is_enum`](#c:is_enum), [`is_struct`](#c:is_struct), [`is_union`](#c:is_union) * **n…**: [`not`](#c:not) * **t…**: [`tgens`](#c:tgens), [`tmeta`](#c:tmeta), [`true`](#c:true), [`tvis`](#c:tvis) * **v…**: [`v_is_named`](#c:v_is_named), [`v_is_tuple`](#c:v_is_tuple), [`v_is_unit`](#c:v_is_unit), [`vmeta`](#c:vmeta) work/macros/0000775000000000000000000000000014763657303010232 5ustar work/macros/Cargo.toml0000664000000000000000000000216114763657303012162 0ustar [package] name = "derive-deftly-macros" version = "1.0.1" edition = "2021" readme = "README.md" authors=["Ian Jackson ", "and the contributors to Rust derive-deftly"] license = "MIT" description="Macros that implement the derive_deftly crate" homepage = "https://gitlab.torproject.org/Diziet/rust-derive-deftly" repository = "https://gitlab.torproject.org/Diziet/rust-derive-deftly" rust-version = "1.56" # After editing this file, you will probably need to run # maint/update-bizarre # to update the "bizarre" testing versions in tests/pub-export/bizarre-* [features] case = ["heck"] expect = ["sha3", "syn/full"] meta-as-expr = ["syn/full"] meta-as-items = ["syn/full"] beta = [] [dependencies] indexmap = ">=1.8, <3" itertools = ">=0.10.1, <0.16" proc-macro-crate = ">=1.1.3, <4" proc-macro2 = "1.0.53" quote = "1" sha3 = { version = "0.10", optional = true } strum = { version = ">=0.24, <0.28", features = ["derive"] } syn = { version = "2.0.53", features = ["extra-traits"] } void = "1" heck = { version = ">=0.4, <0.6", optional = true } [lib] path = "macros.rs" proc-macro = true work/macros/HACKING.md0000664000000000000000000003317414763657303011630 0ustar # **Hacking on derive-deftly (`HACKING.md`)** Rust procedural macros are a somewhat awkward environment, and, especially, testing them can be complex. * [Required reading](#required-reading) * [User-facing documentation](#user-facing-documentation) * [Generated and auto-updated files in the git tree](#generated-and-auto-updated-files-in-the-git-tree) * [`tests/pub-export/bizarre-facade/*` etc., updated by `maint/update-bizarre`](#testspub-exportbizarre-facade-etc-updated-by-maintupdate-bizarre) * [`Cargo.lock`, updated by `nailing-cargo update`.](#cargolock-updated-by-nailing-cargo-update) * [`Cargo.lock.minimal`, updated by `update-minimal-versions`.](#cargolockminimal-updated-by-update-minimal-versions) * [Tables of contents in various `*.md`, updated by `maint/update-tocs`.](#tables-of-contents-in-various-md-updated-by-maintupdate-tocs) * [Cross-references in `reference.md`, updated by `maint/update-reference-xrefs`](#cross-references-in-referencemd-updated-by-maintupdate-reference-xrefs) * [Testing - see `tests/tests.rs`](#testing---see-teststestsrs) * [Reporting errors during template parsing and expansion](#reporting-errors-during-template-parsing-and-expansion) * [Adding an expansion keyword](#adding-an-expansion-keyword) * [Accessing the driver](#accessing-the-driver) * [Expansion keywords with content or arguments](#expansion-keywords-with-content-or-arguments) * [Adding a keyword that can appear in `${paste }` and/or `${CASE }`](#adding-a-keyword-that-can-appear-in-paste--andor-case-) * [Adding a boolean keyword](#adding-a-boolean-keyword) * [clippy](#clippy) * [Updating the pinned clippy (housekeeping task)](#updating-the-pinned-clippy-housekeeping-task) * [clippy `#[allow]`s - strategy and policy](#clippy-allows---strategy-and-policy) * [Updating the pinned Nightly Rust (used in tests and CI)](#updating-the-pinned-nightly-rust-used-in-tests-and-ci) * [Choose which Nightly Rust version to update to](#choose-which-nightly-rust-version-to-update-to) * [Update the nightly version number](#update-the-nightly-version-number) * [Update the cargo-expand version](#update-the-cargo-expand-version) * [Prepare and merge the changes](#prepare-and-merge-the-changes) * [Compatibility testing (and semver updates)](#compatibility-testing-and-semver-updates) ## Required reading derive-deftly uses types and traits from [`syn`] and [`mod@quote`], extensively. It will be very helpful to run one of ```text maint/build-docs-local --dev # or cargo doc --document-private-items --workspace ``` to get a local rendering including for the internal APIs. That will also get a **rendering of this file with working links**, as `target/doc/derive_deftly_macros/_doc_hacking/index.html`. [`NOTES.md`](_doc_notes) has some ideas for the future, which we may or may not implement. (Comments welcome!) ## User-facing documentation Our user-facing documentation is divided between our `rustdoc` documentation and our `mdbook` source. The user guide (currently only an introduction) lives in book/src/*. See the [`mdbook`](https://rust-lang.github.io/mdBook/) documentation for more implementation. To build all the user-facing documentation, run `maint/build-docs-local` from the top-level directory, and look in the directory it tells you. ## Generated and auto-updated files in the git tree The git tree contains some files which are actually maintained by scripts in `maint/`. ### `tests/pub-export/bizarre-facade/*` etc., updated by `maint/update-bizarre` "Bizarre" version of `derive-deftly`, used for [cross-crate compatibility testing](../../pub_b/index.html). CI will check that these outputs are up to date with the normal top-level `Cargo.toml`s, and `pub-b.rs`, from which they are generated. ### `Cargo.lock`, updated by `nailing-cargo update`. Example lockfile. Used in the CI tests, which (in most tests) pin all of our dependencies. If you're not a user of [`nailing-cargo`](https://diziet.dreamwidth.org/tag/nailing-cargo) you can update this simply by copying a `Cargo.lock` made with `cargo update`. ### `Cargo.lock.minimal`, updated by `update-minimal-versions`. Minimal versions of our dependencies, used for CI testing of our MSRV, etc. `update-minimal-versions` runs `cargo +nightly update ...`, so you have to have a Rust Nightly installed. ### Tables of contents in various `*.md`, updated by `maint/update-tocs`. These are inserted at the `` marker. Checked by CI, but it's only a warning if it's not up to date. ### Cross-references in `reference.md`, updated by `maint/update-reference-xrefs` There are `x:...` and `c:...` `
`s, surrounding each heading describing expansions and conditions. And indexes, at the bottom of the file. Again, checked by CI, but it's only a warning if it's not up to date. ## Testing - see `tests/tests.rs` derive-deftly has comprehensive tests. But, they are complicated (mostly because testing proc macros is complicated). You have to use **a particular version of Nightly Rust**. **See [`tests/tests.rs`](../../derive_deftly_tests/index.html)** for information on how to run and update the tests. ## Reporting errors during template parsing and expansion Generally, we use only `syn::Error` as the error type. Use the [`MakeError`] convenience trait's [`.error()`](MakeError::error) method to construct errors. Often, it is a good idea to generate an error pointing at the relevant parts of both the driver and the template; [`MakeError`]'s implementation on [`[ErrorLoc]`](ErrorLoc) is good for this. ## Adding an expansion keyword You need to decide if it should be useable in `${paste }`. Generally, identifiers (or identifier-like things) strings, and types should, and other things shouldn't. For now let's assume it shouldn't be useable in `${paste }`. And, you need to decide if it should be useable as a boolean expression, in `${if }` etc. Again, for now, let's assume not. Add the keyword to [`pub enum SubstDetails`](syntax::SubstDetails) in `syntax.rs`. If the keyword isn't a Rust keyword, use its name precisely, in lowercase. The enum variannt should contain: * Any arguments allowed and supplied, in their parsed form * Markers `O::NotInPaste` and `O::NotInBool`, as applicable. Add the keyword to the parser in `impl ... Parse for Subst`. Use the provided `keyword!` macro. For the markers, use `not_in_paste?` and `not_in_bool?`. The compiler will now insist you add arms to various matches. Most will be obvious. The meat of the expansion - what your new keyword means - is in `SubstDetails::expand`, in `expand.rs`. For an expansion which isn't permitted in `${paste ..}`, call [`out.append_tokens_with()`](framework::ExpansionOutput::append_tokens_with) or [`out.append_tokens()`](framework::ExpansionOutput::append_tokens). You'll also want to add documentation to `doc/reference.md`, arrangements for debug printing in `macros/dbg_allkw.rs`, test cases in `tests/expand/` and maybe `tests/ui/`, and possibly discussion in `book/src/`. ### Accessing the driver Information about the driver (and the current variant and field) is available via [`framework::Context`]. (Use the methods on `Context`, such as [`field()`](framework::Context::field), to get access to the per-field and per-variant details, rather than using `Context.variant` and open-coding the error handling for `None`.) ### Expansion keywords with content or arguments Parse the content from `input`, in the `keyword!` invocation. See `tmeta` et al for an example. Usually it is best to make a Rust type to represent the content or arguments, if there isn't a suitable one already. To parse a boolean expression, use `Subst`. (Probably, in a `Box`, like in `when`). Normally it is best to put the `O::Not...` markers directly in the `SubstDetails` enum variant; that makes it easier to extract them for use in the `match` arms. It is fine to have thsee markers in an argument type *as well*. For a sophisticated example of this, see `SubstMeta`, which allows `... as ...`, except in boolean context. For named arguments, use [`syntax::ParseUsingSubkeywords`]. ### Adding a keyword that can appear in `${paste }` and/or `${CASE }` Removing `O::NotInPaste` marker from a `SubstDetails` variant will allow the template to contain that keyword within `${paste}` and `${CASE}`. You won't be able to call `out.append_tokens` any more. Instead, you must use one of the more specific [`framework::ExpansionOutput`] methods, such as `append_identfrag` or `append_idpath`. ### Adding a boolean keyword This is fairly straightforward. Use `is_enum` (say) as an example. ## clippy We *do* run clippy, but we turn off all `style` and `complexity` lints. In CI, we test with a pinned version of clippy, currently 1.79.0, because clippy often introduces new lints, and we want to handle that breakage in a controlled fashion. If your MR branch fails the clippy job, you can repro locally with: ```text rustup toolchain add 1.79 rustup component add clippy cargo +1.79 clippy --locked --workspace --all-features ``` ### Updating the pinned clippy (housekeeping task) * Update the version in `.gitlab-ci.yml`, and above. * Run the new clippy and fix or allow lints as appropriate. ### clippy `#[allow]`s - strategy and policy We put `#![allow(clippy::style, clippy::complexity)]` in every top-level Rust file. In tests, we have `#![allow(clippy::style, clippy::complexity, clippy::perf)]`. (Some files which are sufficiently simple to not trigger any lints, are lacking these annotations. We'll add them as needed.) Feel free to add an `#[allow]` if it seems like clippy wants you to make the code worse. We often prefer code which isn't "minimal", if it seems clearer, or more consistent with other nearby code, or if it might make future edits easier. For a clippy false positive, link to the upstream bug report, eg `#[allow(clippy::non_minimal_cfg)] // rust-clippy/issues/13007` ## Updating the pinned Nightly Rust (used in tests and CI) The docker image and the nightly version number `+nightly-YYYY-MM-DD` must be kept in sync. `cargo expand` will probably need updating too. ### Choose which Nightly Rust version to update to Use this to select a corresponding Nightly Rust and container image:
To parse the json, You can use a rune like this:
`curl https://www.chiark.greenend.org.uk/~ian/docker-tags-history/rustlang,rust/tags.2025-02-10T13:08+00:00.gz | zcat | jq -r '.results[] | select(.name | match("^nightly-bookworm$")) | .images[] | select(.architecture | match("^amd64"))'` Pick a date just before upstream Rust branched for a release, since that way we'll maybe have a less-buggy nightly. Note the date `YYYY-MM-DD` (from the `tags.` part of the URL) and use the `jq` rune to get the sha256 image digest. (The Docker official way seems to be to visit and look for the `nightly-bookworm` tag, or whatever. However, as far as I can tell historical information is not available, even though the images *are* retained!) ### Update the nightly version number Install the chosen nightly: `rustup toolchain add nightly-YYYY-MM-DD` Then run: ```text TRYBUILD=overwrite MACROTEST=overwrite STDERRTEST=overwrite \ cargo +nightly-YYYY-MM-DD test --workspace --all-features ``` **Inspect the output carefully** before committing. Use `git-grep` on the old nightly date string and fix all the instances. ### Update the cargo-expand version Quite likely, you'll need to update cargo-expand too, since it may not build with the new nightly. Find the most recent version on `crates.io`, and `cargo install --locked --version 1.0.NN cargo-expand` Run the overwriting test rune, above. **Inspect the output carefully** before committing. Edit `.gitlab-ci.yml` with search-and-replace to fix all the occurrences. ### Prepare and merge the changes 1. Make an MR of any preparatory fixes you found you needed to make. Such changes ought to work with all compiler versions, and should be made into an MR of their own. 2. When that has merged, make an MR containing *one commit*: - gitlab image update - `cargo expand` update - consequential changes to expected test outputs - `Cargo.lock` update (minimal-versions ought not to change here) This has to be its own MR because it changes, along the way, things that the `every-commit` test assumes don't change. Splitting it into multiple MRs arranges to refresh those assumptions.

3. Finally, make an MR for any changes you wish to make to this doc. ## Compatibility testing (and semver updates) New template features can be added, and that's just a semver addition, as usual; if a breaking change to a template feature is needed, that is just a semver breaking change. More intrusive or technical changes can cause semver breaks *in crates that export templates*. See [the public API docs for `define_derive_deftly`](macro@define_derive_deftly#you-must-re-export-derive_deftly-semver-implications) and [`template_export_semver_check`](macro@template_export_semver_check). Such breaking changes should be avoided if at all possible. There are complex arrangements for testing that compatibility isn't broken accidentally, mostly in `compat/`. (See `tests/compat/README.md`.) If it is necessary to make such a totally breaking change, consult the git history and see how it was done last time. (`git annotate` on the implementation of `template_export_semver_check` or `git log -G` on a relevant version number may be helpful to find the relevant MR and its commits). work/macros/NOTES.md0000664000000000000000000003164414763657303011454 0ustar # **Notes and plans (`NOTES.md`)** # Future template features ## Stringification See [#64](https://gitlab.torproject.org/Diziet/rust-derive-deftly/-/issues/64). This is a new expansion context, admitting: * Literal strings (including raw strings, but not byte strings) * Expansions (inner template code must also be valid in string context) * Doubled dollars `$$` Not permitted are other kinds of non-expansion tokens, including non-string literals, punctuation, or identifiers. Expansions that are valid in token context are all allowed. Leaf expansions expand to their string representation (`TokenStream as Display`). Expansions that yield types are stringified *as types* (which has an impact on spacing). The precise formatting of the output is not to be relied on. ### Keyword `${string ...}` Body is a template valid in string context. When expanded in token context, expands to a string literal, whose value is all the content strings, concatenated. Within `${string ...}`, literal strings and the results of inner `${string }` expansions (and `$"..."` templates) are not re-quoted. ### Case conversion Case conversion is allowed inside stringification; the contents must be a valid stringification content. Case conversion is then applied to the stringified text. So inside `${stringify }`, case changing doesn't force pasting context. Identifier pasing `${paste }` and `$< ... >` is also allowed, and expands to the string representation of the type. ### Interaction with `$define` User-defined expansions whose body is precisely `${string }` or `$"..."` are not requoted in string context. For other user defined expansions, the content is evaluated as tokens, and that token stream is converted to a string (requoting any string literals it may contain). This is in semantics similar to `${paste }`, and ensures that any particular template text is statically either string context, or not. Examples: ```rust,ignore ${define A { ${string "a"} }} $A ${define B { ${string "b"} }} ${string $B} ${define C { "c" }} ${string $C} ``` Expands to: ```rust,ignore "a" "b" r#""c""# ``` ### String templating `$"..."` This is a convenient syntax for specifying a string template. The leading `$` is followed by a (possbily raw) string literal. The string literal contents is lexed as follows: * Literal text * Expansions in the form `$keyword` (or user-defined `$NAME`). * Doubled dollars `$$` * Possibly, at some point `${keyword ARGS...}` but with limitations. Possible limitation producing an initial level of support is: * the characters `"'` cannot appear in the `ARGS...` * Possibly, at some point `$< ... >` with the same limitation. This template is equivalent to a `${string ...}` containing the same elements. Possibly in the future we might support `${"template with $1 positional arguments" $< blarky $fname >}` or similar. ### Doc attributes We could support `$///` and `$//!`. The compiler turns these into `#[doc(...)]` and `#![doc(...)]`. So we would have to recognise `$#[doc` and `$#![doc`. (This means we would find it hard (or maybe impossible) to use `$#` for anything other than attributes.) The literal would be processed as for `$"..."`. ### Byte strings We could support byte literals maybe. `${string }` doesn't have a way to request them; there would probably have to be `${byte_string }`. `$"..."` does have a natural way: `$b"..."`. Supporting byte strings would mean allowing `b"..."` within (the relevant) string context, posing the possibility of `${string b"non-utf8 bytes"}` which has to be an error. When is this error checked? Is there one kind of string context, or two? Do we allow `${string b"valid utf8"}`? We could detect the invalid utf-8 at expansion time. But perhaps we should declare that we reserve the right to check this statically, and that wouldn't be a breaking change? ## Scopes (for meta, and loops) See [discussion in #36](https://gitlab.torproject.org/Diziet/rust-derive-deftly/-/issues/36#note_3015721) ### Demo - cross product, allowing access to outer repetition ```rust,ignore ${for let a = fields { ${for let b = fields { self_cross_product_contains(${a.fname} ${b.fname}); }} }} ``` ### Fixing awkwardness re optional meta attributes [#40](https://gitlab.torproject.org/Diziet/rust-derive-deftly/-/issues/40) ```rust,ignore ${if let m = fmeta(call) { ${m.meta as expr} (&self.$fname); }} ``` ### Handling meta attribute multiplicity [#36](https://gitlab.torproject.org/Diziet/rust-derive-deftly/-/issues/36) ```rust,ignore ${for let m = fmeta(call) { ${m.meta as expr} (&self.$fname); }} ``` ### Looking for a thing in various places: ```rust,ignore ${if let m = any(fmeta(call), vmeta(fields(call)), tmeta(fields(call))) {..}} ${for let m = all(fmeta(call), vmeta(fields(call)), tmeta(fields(call))) {..}} ${if let m = select1(fmeta(call), fmeta(obsolete_name_for_call)) {..}} ``` ### Meta scope referring to nonexistent meta item? Can a meta item scope refer to a putative, but actually nonexistent, item? Not sure if we need this. [#62](https://gitlab.torproject.org/Diziet/rust-derive-deftly/-/issues/62) I suggest not, in the first instance. ```rust,ignore ${let m = fmeta(call) { ${if ${m.meta} { ... }} }} ``` ### Details of binding in `if ... any` etc. What about mixed binding and non-binding conditions? Options are (a) reject this (b) on the non-binding arms, bind to nonexistent meta. I think I prefer (a). It's the more cautious approach, certainly. ```rust,ignore ${if let m = any(fmeta(call), true) { ${if ${m.meta} { ... }} }} ``` ### Meta scopes vs repeat scopes Every scope is either a meta scope or a repeat scope. `${scope.meta}` is only allowed for meta scopes. Other expansions, including notably `${scope.Xmeta}`, are only allowed for repeat scopes. ```rust,ignore ${for let m = fmeta(call) { .. ${m.fmeta(call)} .. // ERROR, user wrote `fmeta`, wanted `meta` .. ${m.meta(call)} .. // OK }} ${for let m = fields { .. ${m.fmeta(call)} .. // OK .. ${m.meta(call)} .. // ERROR, m isn't a meta scope // user must write ${m.fmeta} }} ``` #### Alternative With a meta scope `m`, the only legal expansion using it is `${m.meta((call)}` or whatever. (Right now; But expansions other than `$Xmeta` might be OK to support. Not `$Xmeta` because it has too much scope for error: since `${if let m = fmeta(call) { .. ${m.fmeta(..)} .. }}` doesn't do at all what the user might expect.) So, instead, we could say that you don't need to write `.meta` and have it be just `${m(call)}`. But: * Now it lives in the same namespace as keywords, and is a user-defined name, so it must be uppercase. `${M(call)}`. * This reduces the similarity between meta scopes and normal scopes. * It would prevent us supporting eg `${m.fname}` in the future, which might be useful with something like `${if let m = find1(field, fmeta(call)) { $m.fname ... }}`. (meaning "find the precisely one field with `#[deftly(call)]`, and then expand stuff with it), or other things I haven't thought of yet. * If we support arguments to user-defined meta items, the syntax for passing them wouldn't look like the meta syntax, so `${M(call)}` is syntactically weird. ### Binding and checking Binding is dynamic (like `${define }`) (despite the use of `let` which is often lexical in other languages including Rust). (Meta attribute checking is dynamic and precise, naturally.) ## Splitting off fields and handling subsets of the generics Syntax and semantics TBD. Some notes: ```text For things that need to split off fields struct Foo as above { and talk only about subsets of the generics field: Box, generic parameter uses (for fields) $fgens T, $fgens_omitted 'l, C For explicit iteration, within ${for tgens ...} or $( ... ) $tgname 'l T C $tgbounds ??? Something for being able to handle structs/unions/enums equally in template, whatever that means. We need to expand something to struct/union/enum, and possibly the brackets and commas in enum { ..., ..., }, and so on. ``` # Future plans wrt macro namespace questions ## Deriving from things other than data structures It would be nice to be able to eventually support deriving from items (traits, fns, ...). This would have to be an attribute proc macro. Attribute proc macros get to modify their applicand, but we would not do that. Ideally that attribute macro would be `#[derive_deftly]`. However: * We are already using that as an inert helper attribute for `#[derive(Deftly)]`. Possibly we could experiment to see how that would interact with a non-inert attribute macro, except that: * It is not possible to have an attribute macro and a function-like macro with the same name; even though the invocation syntaxes (and implementing macro function signatures) are different. ## Proposed taxonomy of macros and attributes We won't implement all of this right away, but it is good to have a plan to make sure the names we take now won't get in the way. * **`#[derive(Deftly)]`**: invokes the from-struct derivation machinery; enables: 1. use of `#[derive_deftly(ReuseableMacro)]` on this very struct 2. later use of `derive_deftly_adhoc!` of the same struct (if `#[derive_defly_adhoc]` specified) 3. `#[deftly(...)]` attributes on bits of the data structure (checked via chaining technique). * **`define_derive_deftly!{ [export] MACNAME: TEMPLATE }`**: define a reusable template, which may be invoked as `#[derive_deftly(MACNAME)]` (within a struct annotated with `#[derive(Deftly)]` or `#[item_derive_deftly(MACNAME)]`. * **`derive_deftly_adhoc!{ DRIVERNAME: TEMPLATE }`**: adhoc derivation from something previously annotated with `#[derive(Deftly)]` or `#[item_derive_deftly]`. `DRIVERNAME` is an item path; we conflate the type and value namespaces. * **`#[derive_defly_adhoc]`**: Inert helper attribute to enable use of `derive_deftly_adhoc!`. * **`#[item_derive_deftly(MACNAME)]`**: attribute macro to be applied to items. The item is reproduced unchanged, except that `#[deftly]` attributes *in places where we would look for them* are filtered out. `#[item_derive_deftly]` will look forward to see if there are further `#[item_derive_deftly]` attributes, so that they can be combined and processed together (this is necessary for correctness of meta attr handling). Template *must* use `for ...` option. `#[derive_deftly_adhoc]` works as usual. It's an error to have `#[item_derive_deftly]` without the `()`. * **`#[deftly]`**: Inert helper attribute for `#[derive(Deftly)]`. Filtered-out attribute for `#[item_derive_deftly]`. Contents available via `$Xmeta`. * **`#[only_derive_deftly]`**: attribute macro to be applied to items; like `#[item_derive_deftly]` but *consumes and does not emit* the item. (We don't really need to be sure about this name; this will be an unusual case and we can give it whatever name seems good, later.) ## consume and not emit: ### Composition problem with `#[deftly]` attributes You should be able to compose mutually-ignorant derive-deftly templates. In particular you should be able to chain transforming templates, and transforming templates should be able to output invocations of normal deriving templates. This won't work without doing "something", because the outer invocation (the first to process the input) will see a bunch of unrecognised `#[deftly]` attributes. I'm not sure what the answer is, but perhaps a template option for accepting `#[deftly]` attrs and `${attr}` for transforming them. Then the caller could `#[deftly(inner(foo))]` and the inner template would receive `#[deftly(foo)]`. Perhaps. ### Possible alternative syntax/naming * **`#[transform_deftly]`**: attribute macro to be applied to items. * d-d option `transform`. Insists that this template is for `#[transform_deftly]` only. ## `for ...` d-d options Currently, we have `for enum`, `for struct`, `for union`. These are fine. We want also want ways to say: * `struct` or `enum`, not `union`: `for data`? * Particular kind of item: `fn`, `trait`, `mod`, `const`. * Any item: `item` (with `#[item_derive_adhoc]` for non-data items). * Combinations of the above: eg `for fn/const`? Outstanding questions, therefore: * Does `for any` mean anything? * What keyword is "`struct`/`enum`"? * Do we need a keyword for `struct`/`enum`/`union`? Probably, since this is going to be the default! * Is `/` the right separator for "or"? ### Internals * **`derive_deftly_engine!`**: Proc macro that does all the work. * **`derive_deftly_driver_DRIVERNAME!`**: `macro_rules` macro generated by `#[derive(Deftly)]` and `#[item_derive_deftly]`, embodying a driver. * **`derive_deftly_template_MACNAME!`**: `macro_rules` macro generated by `define_derive_deftly!`, embodying a template. # Things to check before declaring 1.0 None! But we should get some experience with the renamed crate, probably by upgrading arti to it. work/macros/README.md0000664000000000000000000000010014763657303011500 0ustar Macros for `derive_deftly` **Import `derive_deftly` instead.** work/macros/accum.rs0000664000000000000000000000771514763657303011702 0ustar //! `derive_deftly_engine!()`, parsing accumulations use super::framework::*; use adviseable::*; /// `derive_deftly_engine! accumulated form, accumulated information /// /// We don't reify the whole input; /// instead, we accumulate directly in the `Parse` impl. #[derive(Debug)] pub struct EngineFinalInput { driver: syn::DeriveInput, accum: Accumulated, } #[derive(Debug)] pub struct Accumulated { metas: meta::CheckUsed, } impl EngineFinalInput { pub fn parse_adviseable_remainder( driver: syn::DeriveInput, input: ParseStream, ) -> AdviseableResult { let _empty_next_brackets_contents; let _ = bracketed!(_empty_next_brackets_contents in input); let accum; let _ = bracketed!(accum in input); let accum = accum.parse()?; let _: TokenStream = input.parse()?; Ok(AOk(EngineFinalInput { driver, accum })) } } impl Parse for Accumulated { fn parse(input: ParseStream) -> syn::Result { use meta::CheckUsed as mCU; let mut metas = mCU::Check(meta::Accum::default()); struct Ignore; while !input.is_empty() { let kind: syn::Ident = input.parse()?; match if kind == "_meta_used" { if let mCU::Check(m) = &mut metas { match input.parse()? { mCU::Check(y) => m.used.push(y), mCU::Unchecked => metas = mCU::Unchecked, } continue; } else { Ignore } } else if kind == "_meta_recog" { if let mCU::Check(m) = &mut metas { let content; let _brackets = bracketed!(content in input); let input = content; while !input.is_empty() { use meta::Usage as mU; let allow = match input.parse()? { Some::(_) => mU::BoolOnly, None => mU::Value, }; let desig = input.parse()?; m.recog.update(desig, allow); } continue; } else { Ignore } } else if kind == "error" { metas = mCU::Unchecked; Ignore } else if kind.to_string().starts_with('_') { Ignore } else { return Err( kind.error("unrecognised mandatory accumulation kind") ); } { Ignore => { let _: TokenTree = input.parse()?; } } } Ok(Accumulated { metas }) } } impl EngineFinalInput { pub fn process(self) -> syn::Result { let r = Context::call( &self.driver, &dummy_path(), // template_crate, not used by our f None, // template_name |ctx| { if let mCU::Check(m) = &self.accum.metas { for group in &m.used { adviseable_parse2_call( group.content.clone(), |input| { ctx.decode_update_metas_used(input)?; Ok(AOk(())) }, )? } } let mut errors = ErrorAccumulator::default(); if let mCU::Check(m) = &self.accum.metas { ctx.check_metas_used(&mut errors, &m.recog); } errors.finish() }, ); let mut out = TokenStream::new(); match r { Ok(()) => {} Err(e) => e.into_compile_error().to_tokens(&mut out), } Ok(out) } } work/macros/adhoc.rs0000664000000000000000000000350114763657303011655 0ustar //! Macro impl for adhoc template application `derive_deftly_adhoc!` use super::prelude::*; #[derive(Debug, Clone)] struct TemplateInvocation { driver: syn::Path, options: UnprocessedOptions, colon: Token![:], template: TokenStream, } impl Parse for TemplateInvocation { fn parse(input: ParseStream) -> syn::Result { let driver = input.parse()?; let options = UnprocessedOptions::parse(&input, OpContext::TemplateAdhoc)?; let colon = input.parse()?; let template = input.parse()?; Ok(TemplateInvocation { driver, options, colon, template, }) } } /// This is `derive_deftly_adhoc!` /// /// It parses /// ```rust,ignore /// StructName: /// SOME_TOKENS /// ``` /// and expand it to an invocation of /// ```rust,ignore /// derive_deftly_driver_StructName /// ``` /// as per NOTES.txt. pub fn derive_deftly_adhoc( input: TokenStream, ) -> Result { let TemplateInvocation { driver, options, colon, template, } = syn::parse2(input)?; dprint_block!( [&driver.to_token_stream(), &template], "derive_deftly_adhoc! input", ); let driver_mac_name = { let mut name = driver; let last = name.segments.last_mut().ok_or_else(|| { colon.error( "expected non-empty path for driver struct name, found colon", ) })?; last.ident = format_ident!("derive_deftly_driver_{}", &last.ident); name }; let output = quote! { #driver_mac_name !{ { #template } { ($) } ( crate; [#options] ; ) [] [] } }; dprint_block!(&output, "derive_deftly_adhoc! output"); Ok(output) } work/macros/adviseable.rs0000664000000000000000000001456314763657303012710 0ustar //! errors with compatibility advice //! //! Suitable for local glob import. //! //! ### Non-local errors from syn //! //! `syn` automatically produces "unexpected token" errors, //! if not all of the input is consumed, somewhere. //! //! Empirically: //! these errors are squirreled away somewhere, and surface //! on return from one of the top-level syn `parse` functions //! (the ones that provide a `ParseStream`). //! //! If the top-level function would return `Ok`, //! the unexpected tokens error appears instead. //! But if there's going to be an error anyway, //! the unexpected tokens error is discarded. use super::prelude::*; /// A `Result` whose error might, or might not, need compat advice /// /// * `Err(raw)`: unexpected error, probably mismatched /// derive-deftly versions. /// [`adviseable_parse2`] will return the error /// but with compat advice for the user added. /// * `Ok(ErrNeedsNoAdvice(cooked))`: "expected" error, fully reported for /// the user's benefit. Returned as-is by `parse_advised`. /// * `Ok(Ok(T))`. All went well. /// /// This odd structure is to add a note to most of the errors that /// come out of syn parsing. The `braced!` etc. macros insist that the /// calling scope throws precisely `syn::Error`; `Into` /// isn't enough. /// /// This is the return type of `ParseAdviseable::parse_adviseable`. // // This is all rather unsatisfactory. For example, it implies // the AOk and ErrNNA type aliases and consequent ad-hoc glob imports // of this module. We'd prefer a custom error type, convertible from // syn::Error, but syn won't allow that. pub type AdviseableResult = syn::Result>; /// Typically found as `syn::Result>` #[derive(Debug)] pub enum AdviseableInnerResult { /// Success Ok(T), /// Failure, but doesn't need advice /// /// Typically found as /// `sync::Result::Ok(AdvisedInnerResult::NeedsNoAdvice(..))`. ErrNeedsNoAdvice(syn::Error), } /// Types that can be parsed, but might need compat advice pub trait ParseAdviseable { fn parse_adviseable(input: ParseStream) -> AdviseableResult where Self: Sized; } pub use AdviseableInnerResult::ErrNeedsNoAdvice as ErrNNA; pub use AdviseableInnerResult::Ok as AOk; /// Parses with a callback, and produces advice if necessary /// /// Version of `adviseable_parse2` that takes a callback function, /// rather than a trait impl. pub fn adviseable_parse2_call( input: TokenStream, call: impl FnOnce(ParseStream) -> AdviseableResult, ) -> syn::Result { // If somehow our closure doesn't get called, we want to give // advice, so make that the default. let mut needs_advice = true; let ar = Parser::parse2( |input: ParseStream<'_>| { // When we're returning an error that needs no advice, we must // return *Err* from here, not Ok(NNA), because if we return Ok, // syn might surface an unexpected tokens error instead of the // non-advice-needing error we actually want. // // Encoding the advice-needed status in the error would // be difficult, given how opaque syn::Error is. So // we smuggle a &mut bool into the closure. match call(input) { Err(needs) => Err(needs), Ok(ErrNNA(unadv)) => { needs_advice = false; Err(unadv) } Ok(AOk(y)) => Ok(y), } }, input, ); ar.map_err(|e| { if needs_advice { advise_incompatibility(e) } else { e } }) } /// **Parses `T`, and produces advice if necessary** (principal entrypoint) /// /// All top-level proc_macro entrypoints that want to give advice, /// should ideally call this. /// (See the note in `advise_incompatibility`.) pub fn adviseable_parse2( input: TokenStream, ) -> syn::Result { adviseable_parse2_call(input, T::parse_adviseable) } impl AdviseableInnerResult { pub fn map(self, f: impl FnOnce(T) -> U) -> AdviseableInnerResult { match self { AOk(y) => AOk(f(y)), ErrNNA(e) => ErrNNA(e), } } } /// Add a warning about derive-deftly version incompatibility /// /// ### Lost advice hazard /// /// Take care! /// `syn` generates errors from unprocessed tokens in [`syn::parse2`] etc. /// Calling this function *within* `syn::parse2` /// (ie, somewhere you have a `ParseStream`, /// will result in those errors not receiving advice. /// /// Ideally, call this from functions that have a `TokenStream`. /// If you do that, then functions *isnide* that can /// use this method, avoiding the problem: /// any errors stored up by `syn` will emerge at that call site, /// and be properly dealt with. pub fn advise_incompatibility(err_needing_advice: syn::Error) -> syn::Error { let mut advice = Span::call_site().error( "bad input to derive_deftly_engine inner template expansion proc macro; might be due to incompatible derive-deftly versions(s)" ); advice.combine(err_needing_advice); advice } /// Within `parse_adviseable`, handle errors *without* giving advice /// /// `parse_unadvised! { CONTENT_IDENT => || CLOSURE_BODY }` /// expects `CONTENT_IDENT` to be the contents from /// [`braced!`], [`bracketed!]` or [`parenthesized!`]. /// Calls the closure. /// Errors within the closure won't get advice. /// /// `parse_unadvised! { CONTENT_IDENT }` /// shorthand for calling `.parse()` on the content. /// /// # Sketch /// /// ```rust,ignore /// let something; /// let _ = bracketed!(something in input); /// parse_unadvised! { /// something => || { /// // do something with something, eg something.parse() /// Ok(...) /// } /// } /// ``` macro_rules! parse_unadvised { { $content:ident } => { parse_unadvised! { $content => || $content.parse() } }; { $content:ident => || $( $call:tt )* } => { match syn::parse::Parser::parse2( // We convert the input to the `TokenStream2` and back, // so that we surface "unexpected token errors" here // rather than at the toplevel parsing call. |$content: ParseStream<'_>| -> syn::Result<_> { $($call)* }, $content.parse::()? ) { Ok(y) => y, Err::<_, syn::Error>(e) => return Ok(ErrNNA(e)), } } } work/macros/approx_equal.rs0000664000000000000000000002345314763657303013307 0ustar //! Implementation of `${approx_equal ..}`, and support functions // // # Note on error handling // // Many functions here take `cmp_loc: ErrorLoc` and return `syn::Result`. // `cmp_loc` is the comparison operator (`kw_span` in `boolean.rs`, // referring to the `approx_equal` keyword. // // When generating errors, we include this in our list of ErrorLocs. // // An alternative would be to return a bespoke error type, // consisting of the pieces to make the error from. // I experimented with this, but it's definitely worse. // Also this has trouble handling a `syn::Error` from other code we call. use super::prelude::*; use proc_macro2::Group; use Equality::*; /// Return value of a (perhaps approximate) equality comparison /// /// (Avoids use of `bool`) #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Equality { Equal, Different, } impl Equality { /// Compare `a` and `b` /// /// (Name is short but avoids clash with `Ord::cmp`) pub fn cmpeq(a: &T, b: &T) -> Self { if a == b { Equal } else { Different } } } /// Compare, and return early if different /// /// * **`cmpeq!(d: Equality)`**: /// If `d` is `Different`, returns `Ok(d)`. /// (The containing scope should return `Result`.) /// /// * **`cmpeq!(a: T, b: T);`**: /// compares `a` and `b` using `Equality::cmpeq`, /// and returns immediately if `a != b`, /// or the comparison failed. macro_rules! cmpeq { { $a:expr, $b:expr } => { cmpeq!(Equality::cmpeq(&$a, &$b)); }; { $r:expr } => { if let d @ Different = $r { return Ok(d); } }; } /// Return the input, but with `None`-delimited `Group`s flattened away /// /// Loses some span information. pub fn flatten_none_groups(ts: TokenStream) -> TokenStream { fn recurse(out: &mut TokenStream, input: TokenStream) { for tt in input { match tt { TT::Group(g) if g.delimiter() == Delimiter::None => { recurse(out, g.stream()); } TT::Group(g) => { let span = g.span(); let mut g = Group::new( g.delimiter(), flatten_none_groups(g.stream()), ); // We lose some span information here. g.set_span(span); out.extend([TT::Group(g)]); } _ => out.extend([tt]), } } } let mut out = TokenStream::new(); recurse(&mut out, ts); out } trait LitComparable { fn lc_compare( a: &Self, b: &Self, cmp_loc: &ErrorLoc<'_>, ) -> syn::Result; } trait LitConvertible { type V: Eq; fn lc_convert(&self, cmp_loc: &ErrorLoc<'_>) -> syn::Result; } fn str_check_suffix( suffix: &str, span: Span, cmp_loc: &ErrorLoc<'_>, ) -> syn::Result<()> { if suffix.is_empty() { Ok(()) } else { Err([(span, "literal"), *cmp_loc].error( "comparison of string/byte/character literals with suffixes is not supported" )) } } macro_rules! impl_LitComparable_str { { $lit:ty, $val:ty } => { impl LitConvertible for $lit { type V = $val; fn lc_convert(&self, cmp_loc: &ErrorLoc<'_>) -> syn::Result { str_check_suffix(self.suffix(), self.span(), cmp_loc)?; Ok(self.value()) } } } } impl_LitComparable_str!(syn::LitStr, String); impl_LitComparable_str!(syn::LitByteStr, Vec); impl_LitComparable_str!(syn::LitByte, u8); impl_LitComparable_str!(syn::LitChar, char); impl LitComparable for T { fn lc_compare( a: &Self, b: &Self, cmp_loc: &ErrorLoc<'_>, ) -> syn::Result { Ok(Equality::cmpeq( // &a.lc_convert(cmp_loc)?, &b.lc_convert(cmp_loc)?, )) } } impl LitConvertible for syn::LitBool { type V = (); fn lc_convert(&self, _cmp_loc: &ErrorLoc<'_>) -> syn::Result { Err(self.error( "internal error - TokenTree::Literal parsed as syn::Lit::Bool", )) } } impl LitConvertible for syn::LitFloat { type V = String; fn lc_convert(&self, _cmp_loc: &ErrorLoc<'_>) -> syn::Result { Ok(self.token().to_string()) } } impl LitComparable for syn::LitInt { fn lc_compare( a: &Self, b: &Self, cmp_loc: &ErrorLoc<'_>, ) -> syn::Result { match ( a.base10_parse::(), b.base10_parse::(), ) { (Ok(a), Ok(b)) => Ok(Equality::cmpeq(&a, &b)), (Err(ae), Err(be)) => Err( [(a.span(), &*format!("left: {}", ae)), (b.span(), &*format!("right: {}", be)), *cmp_loc, ].error( "integer literal comparison with both values >u64 is not supported" )), (Err(_), Ok(_)) | (Ok(_), Err(_)) => Ok(Different), } } } fn lit_cmpeq( a: &TokenTree, b: &TokenTree, cmp_loc: &ErrorLoc<'_>, ) -> syn::Result { let mk_lit = |tt: &TokenTree| -> syn::Result { syn::parse2(tt.clone().into()) }; let a = mk_lit(a)?; let b = mk_lit(b)?; syn_lit_cmpeq_approx(a, b, cmp_loc) } /// Compare two literals the way `approx_equal` does /// /// `pub` just so that the tests in `directly.rs` can call it pub fn syn_lit_cmpeq_approx( a: syn::Lit, b: syn::Lit, cmp_loc: &ErrorLoc<'_>, ) -> syn::Result { macro_rules! match_lits { { $( $V:ident )* } => { let mut error_locs = vec![]; for (lit, lr) in [(&a, "left"), (&b, "right")] { match lit { $( syn::Lit::$V(_) => {} )* _ => error_locs.push((lit.span(), lr)), } } if !error_locs.is_empty() { return Err(error_locs.error( "unsupported literal(s) in approx_equal comparison" )); } match (&a, &b) { $( (syn::Lit::$V(a), syn::Lit::$V(b)) => LitComparable::lc_compare(a, b, cmp_loc), )* _ => Ok(Different), } } } // We do not support comparison of `CStr`. // c"..." literals are recognised only by Rust 1.77, // and we would need syn 2.0.59 to parse them. // So this would require // - bumping our syn dependency to 2.0.59 globally, // or somehow making that feature-conditional, // or messing about parsing the lockfile in build.rs. // - Adding an MSRV-influencing feature, // or testing the rustc version in build.rs. // I hoping we can put this off. match_lits! { Str ByteStr Byte Char Bool Int Float } } fn tt_cmpeq( a: TokenTree, b: TokenTree, cmp_loc: &ErrorLoc<'_>, ) -> syn::Result { let discrim = |tt: &_| match tt { TT::Punct(_) => 0, TT::Literal(_) => 1, TT::Ident(_) => 2, TT::Group(_) => 3, }; cmpeq!(discrim(&a), discrim(&b)); match (a, b) { (TT::Group(a), TT::Group(b)) => group_cmpeq(a, b, cmp_loc), (a @ TT::Literal(_), b @ TT::Literal(_)) => lit_cmpeq(&a, &b, cmp_loc), (a, b) => Ok(Equality::cmpeq(&a.to_string(), &b.to_string())), } } fn group_cmpeq( a: Group, b: Group, cmp_loc: &ErrorLoc<'_>, ) -> syn::Result { let delim = |g: &Group| Group::new(g.delimiter(), TokenStream::new()).to_string(); cmpeq!(delim(&a), delim(&b)); ts_cmpeq(a.stream(), b.stream(), cmp_loc) } /// Internal, recursive, comparison of flattened `TokenStream`s fn ts_cmpeq( a: TokenStream, b: TokenStream, cmp_loc: &ErrorLoc<'_>, ) -> syn::Result { for ab in a.into_iter().zip_longest(b) { let (a, b) = match ab { EitherOrBoth::Both(a, b) => (a, b), EitherOrBoth::Left(_) => return Ok(Different), EitherOrBoth::Right(_) => return Ok(Different), }; match tt_cmpeq(a, b, cmp_loc)? { Equal => {} neq => return Ok(neq), } } return Ok(Equal); } /// Compares two `TokenStream`s for "equivalence" /// /// We intend that two `TokenStream`s count as "equivalent" /// if they mean the same thing to the compiler, /// modulo any differences in spans. /// /// We also disregard spacing. This is not 100% justifiable but /// I think there are no token sequences differing only in spacing /// which are *both* valid and which differ in meaning. /// /// ### Why ?! /// /// `< <` and `<<` demonstrate that it is not possible to provide /// a fully correct and coherent equality function on Rust tokens, /// without knowing the parsing context: /// /// In places where `<<` is a shift operator, `< <` is not legal. /// But in places where `<<` introduces two lots of generics, /// `<<` means the same. /// /// I think a function which treats `< <` and `<<` as equal is more useful /// than one that doesn't, because it will DTRT for types. /// /// ### `None`-delimited `Group`s /// /// We flatten these /// /// This is necessary, because otherwise /// apparently-identical pieces of code count as different. /// /// This does mean that two things which are `approx_equal` /// can be expressions with different values! /// /// But, the Rust grammar for types doesn't permit ambiguity, /// so the type equality guarantee of `approx_equal` is preserved. // // Comparing for equality has to be done by steam. // And a lot of stringification. pub fn tokens_cmpeq( a: TokenStream, b: TokenStream, cmp_span: Span, ) -> syn::Result { let a = flatten_none_groups(a); let b = flatten_none_groups(b); ts_cmpeq(a, b, &(cmp_span, "comparison")) } work/macros/beta.rs0000664000000000000000000000621414763657303011516 0ustar //! Handle `beta_deftly` template option, when `beta` cargo feature enabled //! //! For instructions on adding a beta feature, //! see [`beta::Enabled`]. //! //! This is a bit annoying. It has to be an ambient property, //! so that the syn `Parse` trait can be implemented. use super::prelude::*; use std::panic::{catch_unwind, resume_unwind, AssertUnwindSafe}; /// Token indicating that beta feature(s) are or can be enabled /// /// When adding a new beta feature: /// /// * Put an instance of [`beta::Enabled`] /// in the appropriate piece of parsed template syntax, /// For example, in the [`SubstDetails`](super::syntax::SubstDetails) /// along with the `O::` markers. /// /// * When parsing, obtain the value from [`Enabled::new_for_syntax`]. /// /// * Add a test case to `tests/minimal-ui/disabled.rs` /// which *omits* the `beta_deftly` option, and therefore fails, /// thus demonstrating that the feature gate works as intended. /// #[derive(Debug, Copy, Clone, Eq, PartialEq)] #[non_exhaustive] pub struct Enabled {} #[derive(Debug, Copy, Clone, Eq, PartialEq)] enum ThreadState { Unset, Set(Option), } use ThreadState as TS; thread_local! { static ENABLED: Cell = Cell::new(TS::Unset); } /// Workaround for `LazyStatic::get` MSRV of 1.73.0. fn threadlocal_get() -> ThreadState { ENABLED.with(|c| c.get()) } fn threadlocal_set(s: ThreadState) { ENABLED.with(|c| c.set(s)) } /// Call `f` with beta features enabled or not /// /// Used by the parser for `TopTemplate` pub fn with_maybe_enabled( enabled: Option, f: impl FnOnce() -> R, ) -> R { assert_eq!(threadlocal_get(), TS::Unset); threadlocal_set(TS::Set(enabled)); // Unwind safety: we re-throw the panic, // so even if f or R wasn't, no-one observes any broken invariant. let r = catch_unwind(AssertUnwindSafe(f)); threadlocal_set(TS::Unset); match r { Ok(r) => r, Err(e) => resume_unwind(e), } } impl Enabled { /// If the cargo feature is enabled, return `Ok(Enabled)` /// /// Used when parsing the `beta_deftly` template option. // // (In this version of the source code it *always* returns Ok. // Returning Err is done by beta_disabled.rs.) pub fn new_for_dd_option(_: Span) -> syn::Result { Ok(Enabled {}) } /// If the `beta_deftly` template feature is enabled, return `Ok(Enabled)` /// /// Used when parsing beta syntax, in templates. #[allow(dead_code)] // sometimes we might not have any beta features pub fn new_for_syntax(span: Span) -> syn::Result { match threadlocal_get() { TS::Unset => { Err(span.error("internal error! beta::ENABLED Unset")) } TS::Set(ue) => ue.ok_or_else(|| { span.error( "beta derive-deftly feature used, without `beta_deftly` template option" ) }), } } /// Makes `new_for_syntax` work properly within `f`, in test cases #[cfg(test)] #[allow(dead_code)] pub fn test_with_parsing(f: impl FnOnce() -> R) -> R { with_maybe_enabled(Some(Enabled {}), f) } } work/macros/beta_disabled.rs0000664000000000000000000000146514763657303013350 0ustar //! Version of `mod beta` for when the cargo feature is disabled //! //! See `beta.rs` for dev documentation. use super::prelude::*; #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum Enabled {} pub fn with_maybe_enabled(_: Option, f: impl FnOnce() -> R) -> R { f() } impl Enabled { pub fn new_for_dd_option(span: Span) -> syn::Result { Err(span.error( "derive-deftly's `beta_deftly` template option is only available when the `beta` cargo feature is also enabled" )) } #[allow(dead_code)] // sometimes we might not have any beta features pub fn new_for_syntax(span: Span) -> syn::Result { Err(span.error( "beta derive-deftly feature used, which requires both the `beta` cargo feature and the `beta_deftly` template option" )) } } work/macros/boolean.rs0000664000000000000000000001612614763657303012225 0ustar //! Handling of boolean evaluation (conditions) use super::framework::*; /// Implementor of [`SubstParseContext`] for booleans /// /// No values of this type are ever created - /// it's just a generic parameter, used to select the associated /// marker types (and their constructor methods( in SubstParseContext. /// /// So it can be uninhabited. #[derive(Debug)] pub struct BooleanContext(Void); pub struct Found; fn is_found(r: Result<(), Found>) -> bool { r.is_err() } impl SubstParseContext for BooleanContext { type NotInPaste = (); type NotInBool = Void; type BoolOnly = (); const IS_BOOL: bool = true; type DbgContent = Subst; fn not_in_paste(_: &impl Spanned) -> syn::Result<()> { Ok(()) } fn bool_only(_: &impl Spanned) -> syn::Result<()> { Ok(()) } fn meta_recog_usage() -> meta::Usage { meta::Usage::BoolOnly } fn not_in_bool(span: &impl Spanned) -> syn::Result { Err(span.error( "derive-deftly construct is an expansion - not valid in a condition", )) } type SpecialParseContext = (); fn missing_keyword_arguments(kw_span: Span) -> syn::Result { Err(kw_span.error("missing parameters to condition")) } } impl Subst { pub fn eval_bool(&self, ctx: &Context) -> syn::Result { // eprintln!("@@@@@@@@@@@@@@@@@@@@ EVAL {:?}", self); let kw_span = self.kw_span; let v_fields = || ctx.variant(&kw_span).map(|v| &v.fields); use syn::Fields as SF; let expand = |x: &Template<_>| { let mut out = TokenAccumulator::new(); x.expand(ctx, &mut out); let out = out.tokens()?; Ok::(out) }; let r = match &self.sd { SD::is_enum(..) => ctx.is_enum(), SD::is_struct(..) => matches!(ctx.top.data, syn::Data::Struct(_)), SD::is_union(..) => matches!(ctx.top.data, syn::Data::Union(_)), SD::v_is_unit(..) => matches!(v_fields()?, SF::Unit), SD::v_is_tuple(..) => matches!(v_fields()?, SF::Unnamed(..)), SD::v_is_named(..) => matches!(v_fields()?, SF::Named(..)), SD::tgens(_) => !ctx.top.generics.params.is_empty(), SD::Xmeta(sm) => { let meta::SubstMeta { desig: meta::Desig { label, scope: _ }, as_, default, } = sm; match default { None => {} Some((_, nb, _)) => void::unreachable(*nb), }; use meta::SubstAs as mSA; if let Some(as_) = as_ { match as_ { mSA::expr(nb, ..) | mSA::ident(nb, ..) | mSA::items(nb, ..) | mSA::path(nb) | mSA::str(nb) | mSA::token_stream(nb, ..) | mSA::ty(nb) => void::unreachable(*nb), } }; is_found(label.search_eval_bool(sm.pmetas(ctx, kw_span)?)) } SD::is_empty(_, content) => expand(content)?.is_empty(), SD::approx_equal(_, [a, b]) => { tokens_cmpeq(expand(a)?, expand(b)?, kw_span)? == Equality::Equal } SD::UserDefined(name) => name.lookup_eval_bool(ctx)?, SD::False(..) => false, SD::True(..) => true, SD::not(v, _) => !v.eval_bool(ctx)?, SD::any(vs, _) => vs .iter() .find_map(|v| match v.eval_bool(ctx) { Ok(true) => Some(Ok(true)), Err(e) => Some(Err(e)), Ok(false) => None, }) .unwrap_or(Ok(false))?, SD::all(vs, _) => vs .iter() .find_map(|v| match v.eval_bool(ctx) { Ok(true) => None, Err(e) => Some(Err(e)), Ok(false) => Some(Ok(false)), }) .unwrap_or(Ok(true))?, SD::Vis(vis, _) => match vis.syn_vis(ctx, kw_span)? { syn::Visibility::Public(_) => true, _ => false, }, SD::dbg(ddr) => { let r = ddr.content_parsed.eval_bool(ctx); let () = &ddr.content_string; let w = |s: fmt::Arguments| { eprintln!( "derive-deftly dbg condition {} evaluated to {}", ddr.display_heading(ctx), s, ) }; match &r { Ok(y) => w(format_args!("{:?}", y)), Err(e) => w(format_args!("error: {}", e)), }; r? } // ## maint/check-keywords-documented NotInBool ## SD::tname(not_in_bool) | SD::ttype(not_in_bool) | SD::tdeftype(not_in_bool) | SD::vname(not_in_bool) | SD::fname(not_in_bool) | SD::ftype(not_in_bool) | SD::vtype(_, _, not_in_bool) | SD::tdefkwd(not_in_bool) | SD::tattrs(_, _, not_in_bool) | SD::vattrs(_, _, not_in_bool) | SD::fattrs(_, _, not_in_bool) | SD::tdefgens(_, not_in_bool) | SD::tgnames(_, not_in_bool) | SD::twheres(_, not_in_bool) | SD::vpat(_, _, not_in_bool) | SD::fpatname(not_in_bool) | SD::tdefvariants(_, _, not_in_bool) | SD::fdefine(_, _, not_in_bool) | SD::vdefbody(_, _, _, not_in_bool) | SD::paste(_, not_in_bool) | SD::ChangeCase(_, _, not_in_bool) | SD::when(_, not_in_bool) | SD::define(_, not_in_bool) | SD::defcond(_, not_in_bool) | SD::For(_, not_in_bool) | SD::If(_, not_in_bool) | SD::select1(_, not_in_bool) | SD::ignore(_, not_in_bool) | SD::error(_, not_in_bool) | SD::dbg_all_keywords(not_in_bool) | SD::Crate(_, not_in_bool) => void::unreachable(*not_in_bool), }; Ok(r) } } impl DefinitionName { fn lookup_eval_bool(&self, ctx: &Context<'_>) -> syn::Result { let (def, ctx) = ctx.find_definition::(self)?.ok_or_else(|| { let mut error = self.error(format!( "user-defined condition `{}` not found", self, )); if let Some(def) = ctx.definitions.find_raw::(self) { // Condition syntax looks like fine tokens, // so the ${define } wouldn't spot this mistake. error.combine( def.name.error( "this user-defined expansion used as a condition (perhaps you meant ${defcond ?}" ) ); } error })?; def.body.eval_bool(&ctx) } } work/macros/build.rs0000664000000000000000000000153114763657303011677 0ustar // build.rs for derive-deftly-macros // We recompile some of our macro code in tests, with cargo // features that aren't declared in the package's Cargo.toml. // And, we have a nonstandard RUSTFLAGS --cfg=derive_deftly_dprint // for debugging output. // // Here, we tell rustc, via cargo, that these are all allowed. // // Another effect is that this build.rs file // causes OUT_DIR to be available in `check.rs`. // This is a subset of tests/build.rs, written to be as minimal // (and fast to compile) as possible. fn main() { // We must use deprecated single colon `cargo:rustc...` syntax, // because otherwise cargo thinks we're violating our MSRV. // https://github.com/rust-lang/cargo/issues/14147 println!( r#"cargo:rustc-check-cfg=cfg(derive_deftly_dprint) cargo:rustc-check-cfg=cfg(feature, values("bizarre"))"# ); } work/macros/check.rs0000664000000000000000000001402214763657303011654 0ustar //! Implementation of the `expect` option use crate::prelude::*; /// Value for an `expect` #[derive(Debug, Clone, Copy, Eq, PartialEq, EnumString, Display)] #[allow(non_camel_case_types)] pub enum Target { items, expr, } /// Local context for a syntax check operation struct Checking<'t> { ctx: &'t framework::Context<'t>, output: &'t mut TokenStream, target: DdOptVal, } /// Main entrypoint /// /// Checks that `output` can be parsed as `target`. /// /// If not, replaces `output` with something which will generate /// compiler error(s) which the user will find helpful: /// * A `compile_error!` invocation with the original error span /// * include_file!` for a generated temporary file /// containing the text of the output, /// so that the compiler will point to the actual error. pub fn check_expected_target_syntax( ctx: &framework::Context, output: &mut TokenStream, target: DdOptVal, ) { check::Checking { ctx, output, target, } .check(); } pub fn check_expect_opcontext( op: &DdOptVal, context: OpContext, ) -> syn::Result<()> { use OpContext as OC; match (context, op.value) { (OC::TemplateDefinition, Target::items) => Ok(()), (OC::TemplateDefinition, _) => { Err(op.span.error( "predefined templates must always expand to items", // )) } _ => Ok(()), } } impl Target { /// Checks if `ts` can parse as `self`, returning the error if not fn perform_check(self, ts: TokenStream) -> Option { fn chk(ts: TokenStream) -> Option { syn::parse2::>(ts).err() } use Target::*; match self { items => chk::>>(ts), expr => chk::(ts), } } /// Tokens for `include!...` to include syntax element(s) like `self` fn include_syntax(self, file: &str) -> TokenStream { use Target::*; match self { items => quote! { include!{ #file } }, expr => quote! { include!( #file ) }, } } /// Make a single output, syntactically a `self.target`, out of pieces /// /// `err` is a `compile_error!` call, /// and `expansion` is typically the template expansion output. fn combine_outputs( self, mut err: TokenStream, expansion: TokenStream, ) -> TokenStream { use Target::*; match self { items => { err.extend(expansion); err } expr => quote!( ( #err, #expansion ) ), } } } impl Checking<'_> { /// Checks that `tokens` can be parsed as `T` /// /// Does the actual work of [`check_expected_target_syntax`] fn check(self) { let err = self.target.value.perform_check(self.output.clone()); let err = match err { Some(err) => err, None => return, }; let broken = mem::take(self.output); let err = err.into_compile_error(); let expansion = expand_via_file(self.ctx, self.target.value, broken) .map_err(|e| { Span::call_site() .error(format!( "derive-deftly was unable to write out the expansion to a file for fuller syntax error reporting: {}", e )) .into_compile_error() }) .unwrap_or_else(|e| e); *self.output = self.target.value.combine_outputs(err, expansion); } } /// Constructs an `include!` which includes the text for `broken` /// /// Appends the `include` to `checking.output`. /// /// If this can't be done, reports why not. fn expand_via_file( ctx: &framework::Context, target: Target, broken: TokenStream, ) -> Result { use sha3::{Digest as _, Sha3_256}; use std::{fs, io, io::Write as _, path::PathBuf}; let text = format!( "// {}, should have been {}:\n{}\n", ctx.expansion_description(), target, broken, ); let hash: String = { let mut hasher = Sha3_256::new(); hasher.update(&text); let hash = hasher.finalize(); const HASH_LEN_BYTES: usize = 12; hash[0..HASH_LEN_BYTES].iter().fold( String::with_capacity(HASH_LEN_BYTES * 2), |mut s, b| { write!(s, "{:02x}", b).expect("write to String failed"); s }, ) }; let dir: PathBuf = [env!("OUT_DIR"), "derive-deftly~expansions~"] .iter() .collect(); match fs::create_dir(&dir) { Ok(()) => {} Err(e) if e.kind() == io::ErrorKind::AlreadyExists => {} Err(e) => return Err(format!("create dir {:?}: {}", &dir, e)), }; let leaf = format!("dd-{}.rs", hash); let some_file = |leaf: &str| { let mut file = dir.clone(); file.push(leaf); file }; let file = some_file(&leaf); let file = file .to_str() .ok_or_else(|| format!("non UTF-8 path? from env var! {:?}", file))?; // We *overwrite* the file in place. // // This is because it's theoretically possible that multiple calls // to this function, at the same time, might be generating files // with identical contents, and therefore the same name. // // So we open it with O_CREATE|O_WRITE but *not* O_TRUNC, // and write our data, and then declare our job done. // This is idempotent and concurrency-safe. // // There is no need to truncate the file, since all writers // are writing the same text. (If we change the hashing scheme, // we must change the filename too.) let mut fh = fs::OpenOptions::new() .write(true) .create(true) .truncate(false) .open(file) .map_err(|e| format!("create/open {:?}: {}", &file, e))?; fh.write_all(text.as_ref()) .map_err(|e| format!("write {:?}: {}", &file, e))?; Ok(target.include_syntax(file)) } work/macros/compat_syn_1.rs0000664000000000000000000000207314763657303013176 0ustar //! Definitions for compatibility with syn 1 use super::prelude::*; /// syn 2 has this distinct type for start/end delimiters for a group pub use proc_macro2::Span as DelimSpan; //---------- Spanned ---------- pub use syn::spanned::Spanned; //---------- Attribute methods ---------- pub trait AttributeExt1 { /// syn 2 has this as an inherent method fn path(&self) -> &syn::Path; } impl AttributeExt1 for syn::Attribute { fn path(&self) -> &syn::Path { &self.path } } impl AttributeExt12 for syn::Attribute { fn call_in_parens(&self, f: F) -> syn::Result where F: FnOnce(ParseStream<'_>) -> syn::Result, { (|outer: ParseStream<'_>| { let inner; let _paren = parenthesized!(inner in outer); f(&inner) }) .parse2(self.tokens.clone()) } } //---------- VisPublic ---------- pub trait VisPublicExt { fn pub_token(self) -> syn::token::Pub; } impl VisPublicExt for syn::VisPublic { fn pub_token(self) -> syn::token::Pub { self.pub_token } } work/macros/compat_syn_2.rs0000664000000000000000000000266314763657303013204 0ustar //! Definitions for compatibility with syn 2 //! //! This, along with `compat_syn_1.rs` and //! [`compat_syn_common`](super::compat_syn_common) //! exists to minimise the delta in the commit which switches to syn 2. //! //! This approach would also allow us to support *both* syn 1 and syn 2, //! and correspondingly reduce our MSRV back to 1.54, //! eg via cargo features, //! if that turns out to be desirable. // TODO we're committed to syn 2 now, we could maybe remove some of this use super::prelude::*; pub use proc_macro2::extra::DelimSpan; //---------- Spanned ---------- /// Local version of `Spanned` /// /// Works around `Spanned` being sealed in syn 2. /// . /// (Not needed with syn 1, but would be harmless there.) pub trait Spanned { fn span(&self) -> Span; } impl Spanned for T { fn span(&self) -> Span { syn::spanned::Spanned::span(self) } } //---------- Attribute methods ---------- impl AttributeExt12 for syn::Attribute { fn call_in_parens(&self, f: F) -> syn::Result where F: FnOnce(ParseStream<'_>) -> syn::Result, { let list = self.meta.require_list()?; let _paren: syn::token::Paren = match list.delimiter { syn::MacroDelimiter::Paren(p) => p, _ => return Err(list.error("expected parenthesised attributes")), }; f.parse2(list.tokens.clone()) } } work/macros/compat_syn_common.rs0000664000000000000000000000124714763657303014330 0ustar //! Definitions for compatibility with syn, common to 1 and 2 use super::prelude::*; //---------- Attribute handling ---------- /// Helper trait to deal with syn's idea of attribute contents /// /// We expect all our attributes to be parenthesised. pub trait AttributeExt12 { /// Parse the content within an `#[attribute(content)]` /// /// Parses the `content` as `T`. fn parse_in_parens(&self) -> syn::Result { self.call_in_parens(Parse::parse) } /// Like `parse_in_parens`, but takes a parser callback. fn call_in_parens(&self, f: F) -> syn::Result where F: FnOnce(ParseStream<'_>) -> syn::Result; } work/macros/dbg_allkw.rs0000664000000000000000000001564414763657303012540 0ustar use super::framework::*; use std::fmt::Error as E; use std::fmt::Result as R; use std::fmt::Write; struct Out<'c> { out: String, subset_only: Option<&'c WithinVariant<'c>>, } impl Write for Out<'_> { fn write_str(&mut self, s: &str) -> fmt::Result { self.out.write_str(s) } } pub fn dump(ctx: &Context) { let w = (|| { let out = String::new(); let subset_only = match ctx.within_loop { WithinLoop::None => None, WithinLoop::When | WithinLoop::Body => { Some(ctx.variant.expect("within loop, but not variant!")) } }; let mut w = Out { out, subset_only }; let description = format!("derive-deftly expansions dump {}", ctx.display_for_dbg()); writeln!(w, "---------- {} (start) ----------", description)?; dump_whole(&mut w, ctx)?; writeln!(w, "---------- {} (end) ----------", description)?; Ok::<_, E>(w.out) })() .expect("write to String failed"); eprint!("{}", w); } fn template_result(ctx: &Context, templ: TokenStream) -> String { let parser = |input: &ParseBuffer<'_>| Template::parse(input); let templ: Template = parser.parse2(templ).expect("failed to parse own template"); let result = (|| { let mut output = TokenAccumulator::new(); templ.expand(ctx, &mut output); output.tokens() })(); match result { Ok(result) => result.to_string(), Err(e) => format!("", e), } } fn dump_any_one( w: &mut Out, ctx: &Context, show_templ: TokenStream, show_op: &str, make_real_templ: &dyn Fn(TokenStream) -> TokenStream, ) -> R { let show_templ_string = { let mut s = show_templ.to_string(); if let Some(inner) = { s.strip_prefix("$ {") .or_else(|| s.strip_prefix("${")) .and_then(|s| s.strip_suffix('}')) } { s = format!("${{{}}}", inner.trim()); } s }; let lh = format!("{:12} {}", show_templ_string, show_op); let templ = make_real_templ(show_templ); writeln!(w, " {:16} {}", lh, template_result(ctx, templ))?; Ok(()) } fn dump_expand_one(w: &mut Out, ctx: &Context, templ: TokenStream) -> R { dump_any_one(w, ctx, templ, "=>", &|t| t) } fn dump_bool_one(w: &mut Out, ctx: &Context, templ: TokenStream) -> R { let make_real = |templ| quote! { ${if #templ { true } else { false }} }; dump_any_one(w, ctx, templ, "=", &make_real) } macro_rules! expand { { $w_ctx:expr, $($t:tt)* } => { dump_expand_one($w_ctx.0, $w_ctx.1, quote!{ $($t)* })?; } } macro_rules! bool { { $w_ctx:expr, $($t:tt)* } => { dump_bool_one($w_ctx.0, $w_ctx.1, quote!{ $($t)* })?; } } fn dump_whole(mut w: &mut Out, ctx: &Context) -> R { writeln!(w, "top-level:")?; let c = (&mut w, ctx); expand! { c, $tname } expand! { c, $ttype } expand! { c, $tvis } expand! { c, $tgens } expand! { c, $tgnames } expand! { c, $twheres } expand! { c, $tdeftype } expand! { c, $tdefgens } expand! { c, $tdefkwd } expand! { c, ${tdefvariants VARIANTS..} } bool! { c, is_struct } bool! { c, is_enum } bool! { c, is_union } bool! { c, tvis } bool! { c, tgens } expand! { c, $tattrs } // Don't debug dump these. But list them here, so that // check-keywords-documented is happy. (That is nicer than // using the check-keywords-documented exception table.) if false { // Perhaps we should search attributes and dump what would work expand! { c, $tmeta } expand! { c, $vmeta } expand! { c, $fmeta } bool! { c, tmeta } bool! { c, vmeta } bool! { c, fmeta } // Too complex to demonstrate expand! { c, $paste } // Too subtle to demonstrate expand! { c, $crate } // Recursive, would be silly expand! { c, $dbg_all_keywords } // Would throw an error if we expanded it expand! { c, $error } // Control flow, can't sensibly be dumped expand! { c, $when } expand! { c, $if } expand! { c, $select1 } expand! { c, $define } expand! { c, $defcond } expand! { c, $ignore } expand! { c, $dbg } bool! { c, not } bool! { c, all } bool! { c, any } // Requires input arguments to do anything bool! { c, dbg } bool! { c, is_empty } bool! { c, approx_equal } // Vacuous bool! { c, true } bool! { c, false } } if let Some(wv) = w.subset_only { dump_variant(w, ctx, wv)?; dump_user_defined(w, ctx)?; } else { WithinVariant::for_each(ctx, |ctx, wv| dump_variant(w, ctx, wv))?; } Ok(()) } fn variant_heading(w: &mut Out, wv: &WithinVariant) -> R { match wv.variant { None => write!(w, "value")?, Some(v) => write!(w, "variant {}", v.ident)?, }; Ok(()) } fn dump_variant(mut w: &mut Out, ctx: &Context, wv: &WithinVariant) -> R { variant_heading(w, wv)?; writeln!(w, ":")?; let c = (&mut w, ctx); expand! { c, $vname } expand! { c, $vtype } expand! { c, $vpat } expand! { c, ${vdefbody VNAME FIELDS..} } bool! { c, v_is_unit } bool! { c, v_is_tuple } bool! { c, v_is_named } expand! { c, $vattrs } if let Some(_) = w.subset_only { dump_field(w, ctx, ctx.field)?; } else { WithinField::for_each(ctx, |ctx, wf| dump_field(w, ctx, Some(wf)))?; } Ok(()) } fn dump_field(mut w: &mut Out, ctx: &Context, wf: Option<&WithinField>) -> R { variant_heading(w, ctx.variant.expect("heading but not variant!"))?; if let Some(wf) = wf { let fname = wf.fname(Span::call_site()).to_token_stream(); writeln!(w, ", field {}:", fname)?; } else { writeln!(w, ", no field:")?; } let c = (&mut w, ctx); expand! { c, $fname } expand! { c, $ftype } expand! { c, $fvis } expand! { c, $fdefvis } expand! { c, $fpatname } expand! { c, $fdefine } bool! { c, fvis } bool! { c, fdefvis } expand! { c, $fattrs } Ok(()) } fn dump_user_defined(mut w: &mut Out, ctx: &Context) -> R { // evade macro hygiene let mut c; let mut name; macro_rules! print_definitions { { $heading:expr, $B:ty, $body:stmt } => { { let mut set = BTreeSet::new(); for def in ctx.definitions.iter::<$B>().flatten() { set.insert(&def.name); } if !set.is_empty() { writeln!(w, "{}", $heading)?; c = (&mut w, ctx); for n in set { name = n; $body } } } } } print_definitions! { "user-defined expansions:", DefinitionBody, expand! { c, $#name } } print_definitions! { "user-defined conditions:", DefCondBody, bool! { c, #name } } Ok(()) } work/macros/define.rs0000664000000000000000000002105414763657303012034 0ustar //! Macro impl for defining a template `define_derive_deftly!` use super::framework::*; #[derive(Debug, Clone)] struct TemplateDefinition { doc_attrs: Vec, export: Option, templ_name: TemplateName, options: UnprocessedOptions, template: TokenStream, } impl Parse for TemplateDefinition { fn parse(input: ParseStream) -> syn::Result { // This rejects Rust keywords, which is good because // for example `#[derive_deftly(pub)]` ought not to mean to apply // a template called `pub`. See ticket #1. let doc_attrs = input.call(syn::Attribute::parse_outer)?; for attr in &doc_attrs { if !attr.path().is_ident("doc") { return Err(attr .path() .error("only doc attributes are supported")); } } let export = MacroExport::parse_option(input)?; let templ_name = input.parse()?; let options = UnprocessedOptions::parse(&input, OpContext::TemplateDefinition)?; let la = input.lookahead1(); if la.peek(Token![=]) { let equals: Token![=] = input.parse()?; return Err(equals.error( "You must now write `define_derive_deftly! { Template: ... }`, not `Template =`, since derive-deftly version 0.14.0" )); } else if la.peek(Token![:]) { let _colon: Token![:] = input.parse()?; } else { return Err(la.error()); }; let template = input.parse()?; Ok(TemplateDefinition { doc_attrs, export, templ_name, options, template, }) } } /// Replaces every `$` with `$orig_dollar` /// /// Eg, where the template says `$fname`, we emit `$orig_dollar fname`. /// When this is found in the macro_rules expander part /// of a precanned template, /// macro_rules doesn't expand /// it because `orig_dollar` isn't one of the arguments to the macro. /// /// Then, we spot these when parsing the template, and disregard them. /// That is done by /// [`syntax::deescape_orig_dollar`](super::syntax::deescape_orig_dollar). /// /// See `doc/implementation.md` for why this is needed. /// /// This has the weird result that there's a sometimes /// (namely, when using an adhoc, rather than precanned template) /// an undocumented `orig_dollar` expansion keyword, /// with strange behaviour. /// No-one is likely to notice this. /// /// Additionally, if we're turning `$crate` into `$orig_dollar crate`, /// we change the keyword `crate` to `_dd_intern_crate` /// (and `${crate}` likewise), with the span of the original. /// This is necessary to avoid clippy seeing the bare `crate` /// and thinking the user should have written `$crate` /// (whereas, in fact, they did), /// and emitting a spurious lint `crate_in_macro_def`. /// `$_dd_intern_crate` is an internal alias for d-d's `$crate`. /// /// ### Alternative tactics we rejected: /// /// * Pass a literal dollar sign `$` into the template pattern macro, /// capture it with a macro rules parameter `$dollar:tt`, /// and write `$dollar` in the template. /// This gets the span wrong: the span is that of /// the literal dollar, which came from the call site, not the template. /// /// * Use a different syntax in precanned templates: /// have `escape_dollars` convert to that syntax, /// and the template parsing notice this case and /// de-escape the whole template again at the start. /// This involves processing the whole template twice for no reason. /// (And it would involve inventing an additional, different, /// and probably weird, syntax.) /// /// * As above but do the de-escaping on the fly. /// Currently, though, the information about the template context /// is not available to the parser. /// We'd have to pass it in as a thread local, /// or as an extra generic on `SubstContext` /// (producing two monomorphised copies of the whole template engine). pub fn escape_dollars(input: TokenStream) -> TokenStream { enum St { Dollar, DollarBrace, Other, } impl St { fn exp_kw(&self) -> bool { match self { St::Dollar | St::DollarBrace => true, St::Other => false, } } } fn handle_tt(itt: TokenTree, st: St, out: &mut TokenStream) -> St { let ott = match itt { TT::Group(g) => { let delim = g.delimiter(); let span = g.span_open(); let stream = g.stream(); let st = match (st, delim) { (St::Dollar, Delimiter::Brace) => St::DollarBrace, _ => St::Other, }; let stream = handle_ts(stream, st); let mut g = proc_macro2::Group::new(delim, stream); g.set_span(span); TT::Group(g) } TT::Punct(p) if p.as_char() == '$' => { out.extend(quote_spanned! {p.span()=> #p orig_dollar }); return St::Dollar; } TT::Ident(i) if st.exp_kw() && i == "crate" => { out.extend(quote_spanned! {i.span()=> _dd_intern_crate }); return St::Other; } other => other, }; out.extend([ott]); St::Other } fn handle_ts(input: TokenStream, mut st: St) -> TokenStream { let mut out = TokenStream::new(); for itt in input { st = handle_tt(itt, st, &mut out); } out } handle_ts(input, St::Other) } /// This is `define_derive_deftly!` pub fn define_derive_deftly_func_macro( input: TokenStream, ) -> Result { dprint_block!(&input, "define_derive_deftly! input"); let TemplateDefinition { doc_attrs, export, templ_name, options, template, } = syn::parse2(input)?; let mut output = TokenStream::new(); let (template, parsed_template) = { let mut template = template; let parsed = Parser::parse2( { let ue = options.beta_enabled; move |input: ParseStream| TopTemplate::parse(input, ue) }, template.clone(), ) .map_err(|e| { // Make sure the error is emitted e.into_compile_error().to_tokens(&mut output); // But from now on, let's just use an empty template template = TokenStream::new(); // parsed_template becomes Err(()) () }); (template, parsed) }; let _: Result = parsed_template; let template = escape_dollars(template); let templ_mac_name = templ_name.macro_name(); let doc_addendum = (!doc_attrs.is_empty()).then(|| { let addendum = format!( r#" This is a `derive_deftly` template. Do not invoke it directly. To use it, write: `#[derive(Deftly)] #[derive_deftly({})]`."#, templ_name ); quote!( #[doc = #addendum] ) }); let engine_macro; let export_attr; match export { None => { export_attr = quote! {}; engine_macro = engine_macro_name()?; } Some(pub_token) => { let span = pub_token.span(); export_attr = quote_spanned!(span=> #[macro_export]); engine_macro = quote_spanned!(span=> $crate::derive_deftly::derive_deftly_engine); } } // the macro must recent a dollar as its first argument because // it is hard to find a dollar otherwise! output.extend(quote! { #( #doc_attrs )* #doc_addendum #export_attr macro_rules! #templ_mac_name { { { $($driver:tt)* } [ $($aoptions:tt)* ] ( $($future:tt)* ) $($tpassthrough:tt)* } => { #engine_macro! { { $( $driver )* } [ $($aoptions)* ] () { # template } ( $crate; [#options] #templ_name; ) $($tpassthrough)* } }; { $($wrong:tt)* } => { compile_error!{concat!( "wrong input to derive-deftly template macro ", stringify!(#templ_mac_name), "; might be due to incompatible derive-deftly versions(s)", )} }; } }); dprint_block!(&output, "define_derive_deftly! output {}", templ_mac_name); Ok(output) } work/macros/derive.rs0000664000000000000000000002315314763657303012062 0ustar //! Macro impl for capturing the driver `#[derive(Deftly)]` use super::prelude::*; /// Contents of an entry in a `#[derive_deftly(..)]` attribute enum InvocationEntry { Precanned(syn::Path, UnprocessedOptions), } // (CannedName, CannedName, ...) struct InvocationAttr { entries: Punctuated, } /// Contents of an entry in a `#[derive_deftly_adhoc(..)]` attribute #[derive(Default)] struct AdhocAttr { pub_: Option, } impl Parse for InvocationEntry { fn parse(input: ParseStream) -> syn::Result { let entry = if input.lookahead1().peek(Token![pub]) { return Err(input.error("`pub` must be in #[derive_deftly_adhoc]")); } else { let path = syn::Path::parse_mod_style(input)?; let options = if input.peek(syn::token::Bracket) { let tokens; let _bracket = bracketed!(tokens in input); UnprocessedOptions::parse( &tokens, OpContext::DriverApplicationCapture, )? } else { UnprocessedOptions::default() }; InvocationEntry::Precanned(path, options) }; Ok(entry) } } impl Parse for InvocationAttr { fn parse(input: ParseStream) -> syn::Result { let entries = Punctuated::parse_terminated(input)?; Ok(InvocationAttr { entries }) } } fn check_for_misplaced_atrs(data: &syn::Data) -> syn::Result<()> { let attrs = |attrs: &[syn::Attribute]| { for attr in attrs { if let Some(_) = ["derive_deftly", "derive_deftly_adhoc"] .iter() .find(|forbidden| attr.path().is_ident(forbidden)) { return Err(attr.error( "attribute is only meaningful at the data structure toplevel" )); } } Ok(()) }; let fields = |fs: &Punctuated| { for f in fs.iter() { attrs(&f.attrs)?; } Ok(()) }; let variantish = |fs: &syn::Fields| match fs { syn::Fields::Unit => Ok(()), syn::Fields::Named(n) => fields(&n.named), syn::Fields::Unnamed(u) => fields(&u.unnamed), }; let variants = |vs: &Punctuated| { for v in vs.iter() { attrs(&v.attrs)?; variantish(&v.fields)?; } Ok(()) }; match data { syn::Data::Struct(s) => variantish(&s.fields), syn::Data::Union(u) => fields(&u.fields.named), syn::Data::Enum(e) => variants(&e.variants), } } /// Returns the template macro name, for a given template name (as a path) fn templ_mac_name(mut templ_path: syn::Path) -> syn::Result { if templ_path.segments.is_empty() { return Err(templ_path .leading_colon .as_ref() .expect("path with no tokens!") .error("cannot derive_deftly the empty path!")); } let last = templ_path.segments.last_mut().expect("became empty!"); let name = TemplateName::try_from(last.ident.clone())?; last.ident = name.macro_name(); Ok(templ_path) } /// This is #[derive(Deftly)] pub fn derive_deftly( driver_stream: TokenStream, ) -> Result { use engine::ChainNext; let driver: syn::DeriveInput = syn::parse2(driver_stream.clone())?; dprint_block!(&driver_stream, "#[derive(Deftly)] input"); let driver_mac_name = format_ident!("derive_deftly_driver_{}", &driver.ident); let precanned_paths: Vec<(syn::Path, UnprocessedOptions)> = driver .attrs .iter() .map(|attr| { if !attr.path().is_ident("derive_deftly") { return Ok(None); } let InvocationAttr { entries } = attr.parse_in_parens()?; Ok(Some(entries)) }) .flatten_ok() .flatten_ok() .filter_map(|entry| match entry { Err(e) => Some(Err(e)), Ok(InvocationEntry::Precanned(path, options)) => { Some(Ok((path, options))) } }) .collect::>>()?; let adhoc: Option = driver .attrs .iter() .filter(|attr| attr.path().is_ident("derive_deftly_adhoc")) .inspect(|_: &&syn::Attribute| ()) .map(|attr| { let adhoc = match &attr.meta { syn::Meta::Path(_) => AdhocAttr { pub_: None }, syn::Meta::NameValue(nv) => { return Err(nv .eq_token .error("arguments (if any) must be in parens")) } syn::Meta::List(syn::MetaList { path: _, delimiter, tokens, }) => { match delimiter { syn::MacroDelimiter::Paren(_) => Ok(()), syn::MacroDelimiter::Brace(t) => Err(t.span), syn::MacroDelimiter::Bracket(t) => Err(t.span), } .map_err(|span| span.error("expected parentheses"))?; let pub_ = Parser::parse2( MacroExport::parse_option, tokens.clone(), )?; AdhocAttr { pub_ } } }; Ok::(adhoc) }) .inspect(|_: &Result| ()) // allow this attr to be repeated; any pub makes it pub .reduce(|a, b| { let pub_ = chain!(a?.pub_, b?.pub_).next(); Ok(AdhocAttr { pub_ }) }) .transpose()?; check_for_misplaced_atrs(&driver.data)?; let engine_macro = engine_macro_name()?; // If the driver contains any $ tokens, we must do something about them. // Otherwise, they might get mangled by the macro_rules expander. // In particular, the following cause trouble: // `$template`, `$passthrough` - taken as references to the // macro arguments. // `$$` - taken as a reference to the nightly `$$` macro rules feature // (which we would love to use here, but can't yet) // // `$orig_dollar` is a literal dollar which comes from the driver // invocation in invocation.rs. This technique doesn't get the span // right. But getting the span right here is hard without having // a whole new quoting scheme - see the discussion in the doc comment // for `escape_dollars`. // // We can't use the technique we use for the template, because that // technique relies on the fact that it's *us* that parses the template. // But the driver is parsed for us by `syn`. // // Actual `$` in drivers will be very rare. They could only appear in // attributes or the like. So, unlike with templates (which are // full of important `$`s) we can probably live with the wrong spans. let driver_escaped = escape_dollars(driver_stream); let mut output = TokenStream::new(); let mut accum_start = TokenStream::new(); if let Some(adhoc) = adhoc { accum_start.extend(quote!( _meta_used * )); let macro_export = adhoc .pub_ .map(|export| { let macro_export = quote_spanned!(export.span()=> #[macro_export]); Ok::<_, syn::Error>(macro_export) }) .transpose()?; output.extend(quote! { #[allow(unused_macros)] #macro_export macro_rules! #driver_mac_name { { { $($template:tt)* } { ($orig_dollar:tt) $(future:tt)* } $($dpassthrough:tt)* } => { #engine_macro!{ { #driver_escaped } ( ) { $($template)* } $($dpassthrough)* } }; { $($wrong:tt)* } => { compile_error!{concat!( "wrong input to derive-deftly driver inner macro ", stringify!(#driver_mac_name), "; might be due to incompatible derive-deftly versions(s)", )} }; } }); } let (chain_next, chain_rest); { let mut errs = ErrorAccumulator::default(); let mut chain = chain!( precanned_paths .into_iter() .map(|(templ_path, aoptions)| { let call = templ_mac_name(templ_path)?.to_token_stream(); let ao_versions = OpCompatVersions::ours(); let after_driver = quote!( [ #ao_versions #aoptions ] () ); Ok(ChainNext { call, after_driver }) }) .filter_map(|r| errs.handle(r)), [ChainNext { call: engine_macro.clone(), after_driver: quote!( . () ), }], ); chain_next = chain.next().expect("should have been nonempty!"); chain_rest = { let mut rest = TokenStream::new(); for c in chain { c.to_tokens(&mut rest); } rest }; errs.finish()?; } let ChainNext { call, after_driver } = chain_next; output.extend(quote! { #call !{ { #driver } #after_driver [ #chain_rest ] [ #accum_start ] } }); dprint_block!(&output, "#[derive(Deftly)] output for {}", &driver.ident); Ok(output) } work/macros/engine.rs0000664000000000000000000003133514763657303012052 0ustar //! `derive_deftly_engine!()` use super::framework::*; use adviseable::*; /// Input to `derive_deftly_engine!`, principal form (template expansion) /// /// See `implementation.md`, /// especially /// "Overall input syntax for `derive_deftly_engine!` and templates". #[derive(Debug)] struct EngineExpandInput { driver: syn::DeriveInput, options: DdOptions, template: TopTemplate, template_crate: syn::Path, template_name: Option, chain_next: Option, chain_after: TokenStream, accum: TokenStream, } #[derive(Debug)] pub struct ChainNext { pub call: TokenStream, pub after_driver: TokenStream, } enum EngineContext { Expand { opcontext_template: OpContext, options: DdOptions, }, Final {}, } #[derive(Debug)] enum EngineInput { Expand(EngineExpandInput), Final(accum::EngineFinalInput), } impl Parse for ChainNext { fn parse(input: ParseStream) -> syn::Result { let call = input.parse::()?.to_token_stream(); let after_driver; let _ = parenthesized!(after_driver in input); let after_driver = after_driver.parse()?; Ok(ChainNext { call, after_driver }) } } impl ToTokens for ChainNext { fn to_tokens(&self, out: &mut TokenStream) { let ChainNext { call, after_driver } = self; quote!( #call (#after_driver) ).to_tokens(out); } } impl ParseAdviseable for EngineInput { fn parse_adviseable(input: ParseStream) -> AdviseableResult { let driver; let _ = braced!(driver in input); let driver = driver.parse()?; let engine_context; if input.peek(syn::token::Bracket) { // AOPTIONS appears iff we're being invoked for a precanned // template, rather than an adhoc one; it's from the // `#[derive()` application. let tokens; let mut options = DdOptions::default(); let _ = bracketed!(tokens in input); parse_unadvised! { tokens => || { let oc = OpContext::DriverApplicationPassed; options .parse_update(&tokens, oc) } } engine_context = EngineContext::Expand { opcontext_template: OpContext::TemplateDefinition, options, }; } else if input.peek(Token![.]) { let _indicator: Token![.] = input.parse()?; engine_context = EngineContext::Final {}; } else { engine_context = EngineContext::Expand { opcontext_template: OpContext::TemplateAdhoc, options: DdOptions::default(), }; } let future_ignored; let _ = parenthesized!(future_ignored in input); let _: TokenStream = future_ignored.parse()?; let r = match engine_context { EngineContext::Expand { opcontext_template, options, } => EngineExpandInput::parse_adviseable_remainder( driver, options, input, opcontext_template, )? .map(EngineInput::Expand), EngineContext::Final {} => { accum::EngineFinalInput::parse_adviseable_remainder( driver, input, )? .map(EngineInput::Final) } }; Ok(r) } } impl EngineExpandInput { fn parse_adviseable_remainder( driver: syn::DeriveInput, mut options: DdOptions, input: ParseStream, opcontext_template: OpContext, ) -> AdviseableResult { let template; let _ = braced!(template in input); let template_crate; let template_name; { let through_driver; let _ = parenthesized!(through_driver in input); let input = through_driver; template_crate = input.parse()?; let _: Token![;] = input.parse()?; let tokens; let _ = bracketed!(tokens in input); parse_unadvised! { tokens => || { options.parse_update(&tokens, opcontext_template) } } template_name = if input.peek(Token![;]) { None } else { Some(input.parse()?) }; let _: Token![;] = input.parse()?; let _: TokenStream = input.parse()?; } let (chain_next, chain_after); { let chain; let _ = bracketed!(chain in input); let input = chain; chain_next = if !input.is_empty() { Some(input.parse()?) } else { None }; chain_after = input.parse()?; } let accum; let _ = bracketed!(accum in input); let accum = accum.parse()?; let _: TokenStream = input.parse()?; let template = parse_unadvised! { template => || TopTemplate::parse( template, options.beta_enabled, ) }; Ok(AOk(EngineExpandInput { driver, options, template, template_crate, template_name, chain_next, chain_after, accum, })) } } impl<'c> Context<'c> { /// Calls `f` with a top-level [`Context`] for a [`syn::DeriveInput`] /// /// `Context` has multiple levels of references to values created /// here, so we can't easily provide `Context::new()`. pub fn call( driver: &syn::DeriveInput, template_crate: &syn::Path, template_name: Option<&syn::Path>, f: impl FnOnce(Context) -> syn::Result, ) -> Result { let tmetas = preprocess_attrs(&driver.attrs)?; let pvariants_one = |fields| { let pmetas = &tmetas; let pfields = preprocess_fields(fields)?; let pvariant = PreprocessedVariant { fields, pmetas, pfields, }; syn::Result::Ok((Some(()), vec![pvariant])) }; let union_fields; let variants_pmetas: Vec<_>; let (variant, pvariants) = match &driver.data { syn::Data::Struct(ds) => pvariants_one(&ds.fields)?, syn::Data::Union(du) => { union_fields = syn::Fields::Named(du.fields.clone()); pvariants_one(&union_fields)? } syn::Data::Enum(de) => (None, { variants_pmetas = de .variants .iter() .map(|variant| preprocess_attrs(&variant.attrs)) .try_collect()?; izip!(&de.variants, &variants_pmetas) .map(|(variant, pmetas)| { let fields = &variant.fields; let pfields = preprocess_fields(&variant.fields)?; Ok(PreprocessedVariant { fields, pmetas, pfields, }) }) .collect::, syn::Error>>()? }), }; // `variant` is None in enums; otherwise it's Some(()) // and here we convert it to the real WithinVariant for the fields. let variant = variant.map(|()| WithinVariant { variant: None, // not actually a variant fields: pvariants[0].fields, pmetas: &pvariants[0].pmetas, pfields: &pvariants[0].pfields, }); let ctx = Context { top: &driver, template_crate, template_name, pmetas: &tmetas, field: None, variant: variant.as_ref(), pvariants: &pvariants, definitions: Default::default(), nesting_depth: 0, nesting_parent: None, within_loop: WithinLoop::None, }; f(ctx) } } impl EngineExpandInput { fn process(self) -> syn::Result { dprintln!("derive_deftly_engine! crate = {:?}", &self.template_crate); let DdOptions { dbg, driver_kind, expect_target, beta_enabled, // } = self.options; // This was used when parsing EngineExpandInput.template let _: Option<_> = beta_enabled; if let Some(exp) = driver_kind { macro_rules! got_kind { { $($kind:ident)* } => { match &self.driver.data { $( syn::Data::$kind(..) => ExpectedDriverKind::$kind, )* } } } let got_kind = got_kind!(Struct Enum Union); if got_kind != exp.value { return Err([ (exp.span, "expected kind"), (self.driver.span(), "actual kind"), ] .error(format_args!( "template defined for {}, but applied to {}", exp.value, got_kind, ))); } } let outcome = Context::call( &self.driver, &self.template_crate, self.template_name.as_ref(), |ctx| { let mut output = TokenAccumulator::new(); self.template.expand(&ctx, &mut output); let output = output.tokens()?; // dbg!(&&output); if dbg { let description = ctx.expansion_description(); let dump = format!( concat!( "---------- {} (start) ----------\n", "{}\n", "---------- {} (end) ----------\n", ), &description, &output, &description, ); eprint!("{}", dump); } let mut output = output; if let Some(target) = expect_target { check::check_expected_target_syntax( &ctx, &mut output, target, ); } let metas_used = ctx.encode_metas_used(); Ok((output, metas_used)) }, ); let (expanded, metas_used) = match outcome { Ok((expanded, metas_used)) => (Ok(expanded), Ok(metas_used)), Err(e) => (Err(e), Err(())), }; let chain_call; if let Some(ChainNext { call, after_driver }) = &self.chain_next { let driver = &self.driver; let chain_after = &self.chain_after; let mut accum = self.accum.to_token_stream(); if let Some(name) = &self.template_name { accum.extend(quote!( _name [#name] )); } match &metas_used { Ok(metas_used) => { accum.extend(quote!( _meta_used #metas_used )); use meta::FindRecogMetas as _; let mut meta_recog = meta::Recognised::default(); self.template.find_recog_metas(&mut meta_recog); accum.extend(quote!( _meta_recog [#meta_recog] )); } Err(()) => { accum.extend(quote!( _error [] )); } } chain_call = quote! { #call! { { #driver } #after_driver [ #chain_after ] [ #accum ] } } } else { chain_call = TokenStream::new(); }; dprint_block!(&chain_call, "derive_deftly_engine! chain call"); let mut out = expanded.unwrap_or_else(|e| e.into_compile_error()); out.extend(chain_call); Ok(out) } } /// `derive_deftly_engine!` -- implements the actual template engine /// /// In my design, the input contains, firstly, literally the definition /// that #[derive(Deftly)] was applied to (see NOTES.txt). /// Using the literal input, rather than some pre-parsed version, is /// slower, but means that we aren't inventing a nontrivial data format which /// potentially crosses crate boundaries with semver implications. pub fn derive_deftly_engine_func_macro( input: TokenStream, ) -> syn::Result { dprint_block!(&input, "derive_deftly_engine! input"); let input: EngineInput = adviseable_parse2(input)?; match input { EngineInput::Expand(i) => i.process(), EngineInput::Final(i) => i.process(), } } work/macros/expand.rs0000664000000000000000000006417514763657303012074 0ustar //! Expansion of a template into output tokens //! //! Contains the implementations of `fn expand()` //! for the various template types in [`super::syntax`]. use super::framework::*; impl Expand for SubstIf where Template: ExpandInfallible, O: ExpansionOutput, { fn expand(&self, ctx: &Context, out: &mut O) -> syn::Result<()> { for (condition, consequence) in &self.tests { //dbg!(&condition); if condition.eval_bool(ctx)? { //dbg!(&consequence); consequence.expand(ctx, out); return Ok(()); } } if let Some(consequence) = &self.otherwise { //dbg!(&consequence); consequence.expand(ctx, out); } Ok(()) } } impl SubstIf where Template: ExpandInfallible, O: ExpansionOutput, { fn expand_select1(&self, ctx: &Context, out: &mut O) -> syn::Result<()> { let mut found: Result)>, Vec> = Ok(None); for (condition, consequence) in &self.tests { if !condition.eval_bool(ctx)? { continue; } let cspan = condition.span(); let error_loc = |span| (span, "true condition"); match &mut found { Ok(None) => found = Ok(Some((cspan, consequence))), Ok(Some((span1, _))) => { found = Err(vec![ ctx.error_loc(), error_loc(*span1), error_loc(cspan), ]) } Err(several) => several.push(error_loc(cspan)), } } let found = found .map_err(|several| several.error("multiple conditions matched"))? .map(|(_cspan, consequence)| consequence) .or(self.otherwise.as_deref()) .ok_or_else(|| { [ctx.error_loc(), (self.kw_span, "select1 expansion")] .error("no conditions matched, and no else clause") })?; found.expand(ctx, out); Ok(()) } } impl SubstVType { fn expand( &self, ctx: &Context, out: &mut TokenAccumulator, kw_span: Span, self_def: SubstDetails, ) -> syn::Result<()> { let expand_spec_or_sd = |out: &mut _, spec: &Option>, sd: SubstDetails| { if let Some(spec) = spec { spec.expand(ctx, out); Ok(()) } else { sd.expand(ctx, out, kw_span) } }; if !ctx.is_enum() { return expand_spec_or_sd(out, &self.self_, self_def); } // It's an enum. We need to write the main type name, // and the variant. Naively we might expect to just do // TTYPE::VNAME // but that doesn't work, because if TTYPE has generics, that's // TNAME::::VNAME // and this triggers bizarre (buggy) behaviour in rustc - // see rust-lang/rust/issues/108224. // So we need to emit // TNAME::VNAME:: // // The most convenient way to do that seems to be to re-parse // this bit of the expansion as a syn::Path. That lets // us fish out the generics, for writing out later. let mut self_ty = TokenAccumulator::new(); expand_spec_or_sd(&mut self_ty, &self.self_, self_def)?; let self_ty = self_ty.tokens()?; let mut self_ty: syn::Path = syn::parse2(self_ty).map_err(|mut e| { e.combine(kw_span.error( "error re-parsing self type path for this expansion", )); e })?; let mut generics = mem::take( &mut self_ty .segments .last_mut() .ok_or_else(|| { kw_span.error( "self type path for this expansion is empty path!", ) })? .arguments, ); out.append(self_ty); out.append(Token![::](kw_span)); expand_spec_or_sd(out, &self.vname, SD::vname(Default::default()))?; let gen_content = match &mut generics { syn::PathArguments::AngleBracketed(content) => Some(content), syn::PathArguments::None => None, syn::PathArguments::Parenthesized(..) => { return Err([ (generics.span(), "generics"), (kw_span, "template keyword"), ] .error("self type has parenthesised generics, not supported")) } }; if let Some(gen_content) = gen_content { // Normalise `` to `::`. gen_content .colon2_token .get_or_insert_with(|| Token![::](kw_span)); out.append(&generics); } Ok(()) } } impl SubstVPat { // $vpat for struct $tname { $( $fname: $fpatname, ) } // $vpat for enum $tname::$vname { $( $fname: $fpatname, ) } fn expand( &self, ctx: &Context, out: &mut TokenAccumulator, kw_span: Span, ) -> syn::Result<()> { let self_def = SD::tname(Default::default()); SubstVType::expand(&self.vtype, ctx, out, kw_span, self_def)?; let in_braces = braced_group(kw_span, |mut out| { WithinField::for_each(ctx, |ctx, field| { SD::fname::(()) .expand(ctx, &mut out, kw_span)?; out.append_tokens(&(), Token![:](kw_span))?; // Do the expansion with the paste machinery, since // that has a ready-made notion of what fprefix= might // allow, and how to use it. let mut paste = paste::Items::new(kw_span); if let Some(fprefix) = &self.fprefix { fprefix.expand(ctx, &mut paste); } else { paste.append_fixed_string("f_"); } paste.append_identfrag_toks(&field.fname(kw_span))?; paste.assemble(out, None)?; out.append(Token![,](kw_span)); Ok::<_, syn::Error>(()) }) })?; out.append(in_braces); Ok(()) } } impl ExpandInfallible for Template where TemplateElement: Expand, O: ExpansionOutput, { fn expand(&self, ctx_in: &Context, out: &mut O) { let mut ctx_buf; let mut definitions_here = vec![]; let mut defconds_here = vec![]; let mut ctx = ctx_in; for element in &self.elements { macro_rules! handle_definition { { $variant:ident, $store:expr } => { if let TE::Subst(Subst { sd: SD::$variant(def, _), .. }) = element { // Doing this with a macro makes it nice and obvious // to the borrow checker. $store.push(def); ctx_buf = ctx_in.clone(); ctx_buf.definitions.earlier = Some(&ctx_in.definitions); ctx_buf.definitions.here = &definitions_here; ctx_buf.definitions.conds = &defconds_here; ctx = &ctx_buf; continue; } } } handle_definition!(define, definitions_here); handle_definition!(defcond, defconds_here); let () = element .expand(ctx, out) .unwrap_or_else(|err| out.record_error(err)); } } } impl Expand for TemplateElement { fn expand( &self, ctx: &Context, out: &mut TokenAccumulator, ) -> syn::Result<()> { match self { TE::Ident(tt) => out.append(tt.clone()), TE::Literal(tt, ..) => out.append(tt.clone()), TE::LitStr(tt) => out.append(tt.clone()), TE::Punct(tt, _) => out.append(tt.clone()), TE::Group { delim_span, delimiter, template, not_in_paste: _, } => { use proc_macro2::Group; let mut content = TokenAccumulator::new(); template.expand(ctx, &mut content); let mut group = Group::new(*delimiter, content.tokens()?); group.set_span(*delim_span); out.append(TT::Group(group)); } TE::Subst(exp) => { exp.expand(ctx, out)?; } TE::Repeat(repeated_template) => { repeated_template.expand(ctx, out); } } Ok(()) } } impl Expand for Subst where O: ExpansionOutput, TemplateElement: Expand, { fn expand(&self, ctx: &Context, out: &mut O) -> syn::Result<()> { self.sd.expand(ctx, out, self.kw_span) } } impl SubstDetails where O: ExpansionOutput, TemplateElement: Expand, { /// Expand this template element, by adding it to `O` /// /// This is done using `O`'s [`ExpansionOutput`] methods. fn expand( &self, ctx: &Context, out: &mut O, kw_span: Span, ) -> syn::Result<()> { // eprintln!("@@@@@@@@@@@@@@@@@@@@ EXPAND {:?}", self); let do_meta = |sm: &meta::SubstMeta<_>, out, meta| { sm.expand(ctx, kw_span, out, meta) }; // Methods for handling generics. Most take `composable: bool`, // which lets us control the trailing comma. This is desirable // because we should include it for expansions like $tgens that the // user can append things to, but ideally *not* for expansions like // $ttype that the user can't. let do_tgnames = |out: &mut TokenAccumulator, composable| { for pair in ctx.top.generics.params.pairs() { use syn::GenericParam as GP; match pair.value() { GP::Type(t) => out.append(&t.ident), GP::Const(c) => out.append(&c.ident), GP::Lifetime(l) => out.append(&l.lifetime), } out.append_maybe_punct_composable(&pair.punct(), composable); } }; let do_tgens_nodefs = |out: &mut TokenAccumulator| { for pair in ctx.top.generics.params.pairs() { use syn::GenericParam as GP; let out_attrs = |out: &mut TokenAccumulator, attrs: &[_]| { attrs.iter().for_each(|attr| out.append(attr)); }; match pair.value() { GP::Type(t) => { out_attrs(out, &t.attrs); out.append(&t.ident); out.append(&t.colon_token); out.append(&t.bounds); } GP::Const(c) => { out_attrs(out, &c.attrs); out.append(&c.const_token); out.append(&c.ident); out.append(&c.colon_token); out.append(&c.ty); } GP::Lifetime(l) => out.append(&l), } out.with_tokens(|out| { pair.punct().to_tokens_punct_composable(out); }); } }; let do_tgens = |out: &mut TokenAccumulator, composable: bool| { out.append_maybe_punct_composable( &ctx.top.generics.params, composable, ); }; // There are three contexts where the top-level type // name might occur with generics, and two syntaxes: // referring to the type $ttype Type:: // impl'ing for the type $ttype Type:: // defining a new type $tdeftype Type // Handles $ttype and $tdeftype, and, indirectly, $vtype let do_ttype = |out: &mut O, colons: Option<()>, do_some_gens| { let _: &dyn Fn(&mut _, bool) = do_some_gens; // specify type let gens = &ctx.top.generics; let colons = gens .lt_token .and_then(|_| colons.map(|()| Token![::](kw_span))); out.append_idpath( kw_span, |_| {}, &ctx.top.ident, |out| { out.append(colons); out.append(gens.lt_token); do_some_gens(out, false); out.append(gens.gt_token); }, Grouping::Ungrouped, ) .unwrap_or_else(|e| e.unreachable()) }; let do_maybe_delimited_group = |out, np, delim, content| { let _: &mut O = out; let _: &Template = content; out.append_tokens_with(np, |out| { if let Some(delim) = delim { out.append(delimit_token_group( delim, kw_span, |inside: &mut TokenAccumulator| { Ok(content.expand(ctx, inside)) }, )?); } else { content.expand(ctx, out); } Ok(()) }) }; match self { SD::tname(_) => out.append_identfrag_toks(&ctx.top.ident)?, SD::ttype(_) => do_ttype(out, Some(()), &do_tgnames), SD::tdeftype(_) => do_ttype(out, None, &do_tgens), SD::vname(_) => { out.append_identfrag_toks(&ctx.syn_variant(&kw_span)?.ident)? } SD::fname(_) => { let fname = ctx.field(&kw_span)?.fname(kw_span); out.append_identfrag_toks(&fname)?; } SD::ftype(_) => { let f = ctx.field(&kw_span)?; out.append_syn_type( kw_span, f.field.ty.clone(), Grouping::Invisible, ); } SD::fpatname(_) => { let f = ctx.field(&kw_span)?; let fpatname = Ident::new(&format!("f_{}", f.fname(kw_span)), kw_span); out.append_identfrag_toks(&fpatname)?; } SD::Xmeta(sm) => do_meta(sm, out, sm.pmetas(ctx, kw_span)?)?, SD::error(e, _) => e.throw(ctx)?, SD::Vis(vis, np) => { out.append_tokens(np, vis.syn_vis(ctx, kw_span)?)? } SD::tdefkwd(_) => { fn w(out: &mut O, t: impl ToTokens) where O: ExpansionOutput, { out.append_identfrag_toks(&TokenPastesAsIdent(t)) .unwrap_or_else(|e| e.unreachable()); } use syn::Data::*; match &ctx.top.data { Struct(d) => w(out, &d.struct_token), Enum(d) => w(out, &d.enum_token), Union(d) => w(out, &d.union_token), }; } SD::tattrs(ra, np, ..) => out.append_tokens_with(np, |out| { ra.expand(ctx, out, &ctx.top.attrs) })?, SD::vattrs(ra, np, ..) => out.append_tokens_with(np, |out| { let variant = ctx.variant(&kw_span)?.variant; let attrs = variant.as_ref().map(|v| &*v.attrs); ra.expand(ctx, out, attrs.unwrap_or_default()) })?, SD::fattrs(ra, np, ..) => out.append_tokens_with(np, |out| { ra.expand(ctx, out, &ctx.field(&kw_span)?.field.attrs) })?, SD::tgens(np, ..) => out.append_tokens_with(np, |out| { do_tgens_nodefs(out); Ok(()) })?, SD::tdefgens(np, ..) => out.append_tokens_with(np, |out| { do_tgens(out, true); Ok(()) })?, SD::tgnames(np, ..) => out.append_tokens_with(np, |out| { do_tgnames(out, true); Ok(()) })?, SD::twheres(np, ..) => out.append_tokens_with(np, |out| { if let Some(clause) = &ctx.top.generics.where_clause { out.with_tokens(|out| { clause.predicates.to_tokens_punct_composable(out); }); } Ok(()) })?, SD::vpat(v, np, ..) => out.append_tokens_with(np, |out| { // This comment prevents rustfmt making this unlike the others v.expand(ctx, out, kw_span) })?, SD::vtype(v, np, ..) => out.append_tokens_with(np, |out| { v.expand(ctx, out, kw_span, SD::ttype(Default::default())) })?, SD::tdefvariants(content, np, ..) => { let delim = if ctx.is_enum() { Some(Delimiter::Brace) } else { None }; do_maybe_delimited_group(out, np, delim, content)?; } SD::fdefine(spec_f, np, ..) => { out.append_tokens_with(np, |out| { let field = ctx.field(&kw_span)?.field; if let Some(driver_f) = &field.ident { if let Some(spec_f) = spec_f { spec_f.expand(ctx, out); } else { out.append(driver_f); } } out.append(&field.colon_token); Ok(()) })? } SD::vdefbody(vname, content, np, ..) => { use syn::Fields as SF; let variant = ctx.variant(&kw_span)?; let struct_variant = variant.is_struct_toplevel_as_variant(); if !struct_variant { vname.expand(ctx, out); } let delim = match variant.fields { SF::Unit => None, SF::Unnamed(..) => Some(Delimiter::Parenthesis), SF::Named(..) => Some(Delimiter::Brace), }; do_maybe_delimited_group(out, np, delim, content)?; if !struct_variant { // Any enum variant: terminate with a comma. out.append_tokens(np, Token![,](kw_span))?; } else if matches!(variant.fields, SF::Named(_)) { // struct {} at top-level: no terminator. } else { // Unit or tuple struct: Terminate with a semicolon. out.append_tokens(np, Token![;](kw_span))?; } } SD::Crate(np, ..) => out.append_tokens(np, &ctx.template_crate)?, SD::paste(content, ..) => { paste::expand(ctx, kw_span, content, out)?; } SD::ChangeCase(content, case, ..) => { let mut items = paste::Items::new(kw_span); content.expand(ctx, &mut items); items.assemble(out, Some(*case))?; } SD::define(..) | SD::defcond(..) => out.write_error( &kw_span, // I think this is impossible. It could only occur if // someone parsed a Subst or SubstDetails that wasn't // in a Template. It is Template.expand() that handles this. // We could possibly use proof tokens to see if this happens // and exclude it, but that would be super invasive. // // (There are some parallels between this and `${when }`) "${define } and ${defcond } only allowed in a full template", ), SD::UserDefined(name) => name.lookup_expand(ctx, out)?, SD::ignore(content, _) => { let mut ignore = O::new_with_span(kw_span); content.expand(ctx, &mut ignore); let () = ignore.ignore_impl()?; } SD::when(..) => out.write_error( &kw_span, "internal error - misplaced ${when } detected too late!", ), SD::If(conds, ..) => conds.expand(ctx, out)?, SD::select1(conds, ..) => conds.expand_select1(ctx, out)?, SD::For(repeat, _) => repeat.expand(ctx, out), SD::dbg(ddr) => ddr.expand(ctx, out, kw_span), SD::dbg_all_keywords(_) => dbg_allkw::dump(ctx), // ## maint/check-keywords-documented BoolOnly ## SD::is_struct(bo) | SD::is_enum(bo) | SD::is_union(bo) | SD::v_is_unit(bo) | SD::v_is_tuple(bo) | SD::v_is_named(bo) | SD::is_empty(bo, _) | SD::approx_equal(bo, _) | SD::False(bo) | SD::True(bo) | SD::not(_, bo) | SD::any(_, bo) | SD::all(_, bo) => out.append_bool_only(bo), }; Ok(()) } } impl DbgDumpRequest { fn expand(&self, ctx: &Context, out: &mut O, kw_span: Span) { let desc = format!("derive-deftly dbg dump {}", self.display_heading(ctx),); let mut msg = String::new(); let () = self.content_string; writeln!( msg, // r#"---------- {} expansion (start) ----------"#, desc, ) .expect("write to String failed"); out.dbg_expand(kw_span, ctx, &mut msg, &self.content_parsed); writeln!( msg, r#" ---------- {} expansion (end) ----------"#, desc ) .expect("write to String failed"); eprint!("{}", msg); } } impl ExplicitError { pub fn throw(&self, ctx: &Context<'_>) -> Result { Err([ ctx.error_loc(), // (self.message.span(), "template"), ] .error(self.message.value())) } } impl DefinitionName { fn lookup_expand( &self, ctx: &Context<'_>, out: &mut O, ) -> syn::Result<()> { let (def, ctx) = ctx.find_definition(self)?.ok_or_else(|| { self.error(format!("user-defined expansion `{}` not found", self)) })?; match &def.body { DefinitionBody::Paste(content) => { paste::expand(&ctx, def.body_span, content, out)?; } DefinitionBody::Normal(content) => { let not_in_paste = O::not_in_paste(self).map_err(|mut unpasteable| { unpasteable.combine(def.body_span.error( "user-defined expansion is not pasteable because it isn't, itself, ${paste }" )); unpasteable })?; out.append_tokens_with(¬_in_paste, |out| { content.expand(&ctx, out); Ok(()) })?; } } Ok(()) } } impl RawAttr { fn expand( &self, ctx: &Context, out: &mut TokenAccumulator, attrs: &[syn::Attribute], ) -> syn::Result<()> { for attr in attrs { match self { RawAttr::Default => { if ["deftly", "derive_deftly", "derive_deftly_adhoc"] .iter() .all(|exclude| !attr.path().is_ident(exclude)) { out.append(attr); } } RawAttr::Include { entries } => { let ent = entries.iter().find(|ent| ent.matches(attr)); if let Some(ent) = ent { ent.expand(ctx, out, attr)?; } } RawAttr::Exclude { exclusions } => { if !exclusions.iter().any(|excl| excl == attr.path()) { out.append(attr); } } } } Ok(()) } } impl RawAttrEntry { fn matches(&self, attr: &syn::Attribute) -> bool { &self.path == attr.path() } fn expand( &self, _ctx: &Context, out: &mut TokenAccumulator, attr: &syn::Attribute, ) -> syn::Result<()> { out.append(attr); Ok(()) } } impl ExpandInfallible for RepeatedTemplate where Template: ExpandInfallible, O: ExpansionOutput, { fn expand(&self, ctx: &Context, out: &mut O) { // for_with_within expects a fallible closure, but we want to do // infallible work in our infallible context, so we use `Void` // as the error type and wrap each call in `Ok`. #[allow(clippy::unit_arg)] // clippy wants us to worsify the style match self.over { RO::Variants => ctx.for_with_within(|ctx, _: &WithinVariant| { Ok::<_, Void>(self.expand_inner(ctx, out)) }), RO::Fields => ctx.for_with_within(|ctx, _: &WithinField| { Ok::<_, Void>(self.expand_inner(ctx, out)) }), } .void_unwrap() } } impl RepeatedTemplate { /// private, does the condition fn expand_inner(&self, ctx: &Context, out: &mut O) where Template: ExpandInfallible, O: ExpansionOutput, { let mut ctx = ctx.clone(); ctx.within_loop = WithinLoop::When; for when in &self.whens { match when.eval_bool(&ctx) { Ok(true) => continue, Ok(false) => return, Err(e) => { out.record_error(e); return; } } } ctx.within_loop = WithinLoop::Body; self.template.expand(&ctx, out) } } work/macros/framework.rs0000664000000000000000000004652714763657303012613 0ustar //! Core types and traits for parsing and expansion //! //! Also re-exports the names that the implementation wants. //! //! Should be included with `use super::framework::*`, not `crate::`, //! so that it works with `tests/directly.rs` too. pub use super::prelude::*; pub use super::boolean::*; pub use super::repeat::*; pub use super::syntax::*; pub(super) use super::paste; pub(super) use super::paste::{IdentFrag, IdentFragInfallible}; /// Context during expansion /// /// References the driver, and digested information about it. /// Also represents where in the driver we are, /// including repetition context. #[derive(Debug, Clone)] pub struct Context<'c> { pub top: &'c syn::DeriveInput, pub template_crate: &'c syn::Path, pub template_name: Option<&'c syn::Path>, pub pmetas: &'c meta::PreprocessedMetas, pub variant: Option<&'c WithinVariant<'c>>, pub field: Option<&'c WithinField<'c>>, pub within_loop: WithinLoop, pub pvariants: &'c [PreprocessedVariant<'c>], pub definitions: Definitions<'c>, pub nesting_depth: u16, pub nesting_parent: Option<(&'c Context<'c>, &'c DefinitionName)>, } #[derive(Debug)] pub struct PreprocessedVariant<'c> { pub fields: &'c syn::Fields, pub pmetas: &'c meta::PreprocessedMetas, pub pfields: Vec, } #[derive(Debug)] pub struct PreprocessedField { pub pmetas: meta::PreprocessedMetas, } #[derive(Debug, Clone)] pub struct WithinVariant<'c> { pub variant: Option<&'c syn::Variant>, pub fields: &'c syn::Fields, pub pmetas: &'c meta::PreprocessedMetas, pub pfields: &'c [PreprocessedField], } #[derive(Debug, Clone)] pub struct WithinField<'c> { pub field: &'c syn::Field, pub pfield: &'c PreprocessedField, pub index: u32, } /// Whether we're in a loop, and if so, its details /// /// Set only for expansions of a `RepeatedTemplate`, /// not any kind of implicit looping eg `dbg_all_keywords`, `vpat`, etc. /// /// At some future point this may have enough information /// to provide `$loop_index`, etc. /// Right now it's only used for `dbg_all_keywords`. #[derive(Debug, Clone, Copy)] pub enum WithinLoop { None, /// Evaluating `${when }` clauses When, /// Evaluating the body Body, } #[derive(Debug, Clone, Copy, Default)] pub struct Definitions<'c> { pub here: &'c [&'c Definition], pub conds: &'c [&'c Definition], pub earlier: Option<&'c Definitions<'c>>, } /// Special processing instructions returned by /// [`special_before_element_hook`](SubstParseContext::special_before_element_hook) pub enum SpecialInstructions { /// This template is finished /// /// Stop parsing this `Template` though perhaps /// the surrounding `Group` is not finished. /// /// The parser for whatever called `Template::parse` /// will continue. EndOfTemplate, } /// Surrounding lexical context during parsing /// /// This is the kind of lexical context a piece of a template appears in. /// It is implemented for /// * Types that represent an expansion output `ExpansionOutput`; /// in this case, the lexical context is one where /// the expansion is accumulated in this type. /// * Places where template substitution syntax `${keyword }` /// appears but where no output will be generated (eg, within /// the condition of `${if }`. /// /// The associated types are either `Void` or `()`. /// They appears within the variants of `SubstDetails`, /// causing inapplicable variants to be eliminated. /// /// Because a variant is only inhabited if all of its fields are, /// the conditions are effectively ANDed. /// So the "default" value (for context that don't have an opnion) /// is inhabitedness `()`. /// /// Each type has an associated constructur, /// used during parsing. /// So this generates a parse error at parse time, /// if a construct appears in the wrong place. pub trait SubstParseContext: Sized { /// Uninhabited iff this lexical context is within `${paste }` type NotInPaste: Debug + Copy + Sized; /// Uninhabited iff this lexical context is within a condition. type NotInBool: Debug + Copy + Sized; /// Uninhabited unless this lexical context is within a condition. type BoolOnly: Debug + Copy + Sized; /// Whether this is a boolean context // // Useful for ad-hoc handling of the way that boolean // context has a different notion of syntax. const IS_BOOL: bool = false; /// Content of the `dbg` keyword /// /// This has to be in this trait because /// `${dbg }` contains a `Template` but `dbg(...)` contains a `Subst`. /// /// We make bespoke output for each context; for boolean this is sui /// generis, and for expansions it's in [`ExpansionOutput::dbg_expand`]. type DbgContent: Parse + Debug + AnalyseRepeat + meta::FindRecogMetas; fn not_in_paste(span: &impl Spanned) -> syn::Result; fn not_in_bool(span: &impl Spanned) -> syn::Result; fn bool_only(span: &impl Spanned) -> syn::Result { Err(span.error( "derive-deftly keyword is a condition - not valid as an expansion", )) } /// When we find a `fmeta` etc. in this context, does it allow a value? /// /// Used by the template-scanning code, to report whether an `Xmeta` /// in the template justifies a value-bearing `Xmeta` attribute /// on/in the driver, or just a boolean. fn meta_recog_usage() -> meta::Usage; /// For communicating through `parse_special` type SpecialParseContext: Default; /// Handle any special syntax for a special kind of template context. /// /// This method is called only when parsing multi-element [`Template`]s, /// It's a hook, called before parsing each `TemplateElement`. /// /// It should consume any special syntax as appropriate, /// /// The default implementation is a no-op. /// The only non-default implementation is in `paste.rs`, for `$<...>` - /// see [`paste::AngleBrackets`]. fn special_before_element_hook( _special: &mut Self::SpecialParseContext, _input: ParseStream, ) -> syn::Result> { Ok(None) } /// Parse using `f`, within parens in boolean context, not otherwise /// /// Useful for parsing the arguments to an argument-taking keyword /// which takes an "equivalent" syntax in both contexts. fn parse_maybe_within_parens( input: ParseStream, f: impl FnOnce(ParseStream) -> syn::Result, ) -> syn::Result { if Self::IS_BOOL { let inner; let _ = parenthesized!(inner in input); f(&inner) } else { f(input) } } /// Parse maybe a comma (comma in boolean contegxt, not otherwise) /// /// Useful for parsing the arguments to an argument-taking keyword /// which takes an "equivalent" syntax in both contexts. fn parse_maybe_comma(input: ParseStream) -> syn::Result<()> { if Self::IS_BOOL { let _: Token![,] = input.parse()?; } Ok(()) } /// Return an error suitable for reporting missing arguments /// /// Helper for handling missing arguments to an argument-taking keyword /// which takes an "equivalent" syntax in both contexts. fn missing_keyword_arguments(kw_span: Span) -> syn::Result { Err(kw_span.error(format_args!( "missing parameters to expansion keyword (NB: argument must be within {{ }})", ))) } } /// Expansion output accumulator, for a template lexical context /// /// Each template lexical context has a distinct type which /// * Represents the lexical context /// * If that lexical context generates expansions, /// accumulates the expansion. That's what this trait is. /// /// The methods are for accumulating various kinds of things /// that can be found in templates, or result from template expansion. /// /// The accumulating type (`Self` might be accumulating /// tokens ([`TokenStream`]) or strings ([`paste::Items`]). pub trait ExpansionOutput: SubstParseContext { /// An identifier (or fragment of one) /// /// Uses the `IdentFragment` for identifier pasting, /// and the `ToTokens` for general expansion. fn append_identfrag_toks( &mut self, ident: &I, ) -> Result<(), I::BadIdent>; /// Append a Rust path (scoped identifier, perhaps with generics) /// /// To facilitate `${pawte }`, the path is provided as: /// * some prefix tokens (e.g., a scoping path), /// * the actual identifer, /// * some suffix tokens (e.g. generics). /// /// `tspan` is the span of the part of the template /// which expanded into this path. /// /// This is a "more complex" expansion, /// in the terminology of the template reference: /// If a paste contains more than one, it is an error. fn append_idpath( &mut self, template_entry_span: Span, pre: A, ident: &I, post: B, grouping: Grouping, ) -> Result<(), I::BadIdent> where A: FnOnce(&mut TokenAccumulator), B: FnOnce(&mut TokenAccumulator), I: IdentFrag; /// Append a [`syn::LitStr`](struct@syn::LitStr) /// /// This is its own method because `syn::LitStr` is not `Display`, /// and we don't want to unconditionally turn it into a string /// before retokenising it. fn append_syn_litstr(&mut self, v: &syn::LitStr); /// Append a [`syn::Type`] /// /// This is a "more complex" expansion, /// in the terminology of the template reference: /// If a paste contains more than one, it is an error. fn append_syn_type( &mut self, te_span: Span, mut v: syn::Type, mut grouping: Grouping, ) { loop { let (inner, add_grouping) = match v { syn::Type::Paren(inner) => (inner.elem, Grouping::Parens), syn::Type::Group(inner) => (inner.elem, Grouping::Invisible), _ => break, }; v = *inner; grouping = cmp::max(grouping, add_grouping); } if let syn::Type::Path(tp) = &mut v { typepath_add_missing_argument_colons(tp, te_span); } self.append_syn_type_inner(te_span, v, grouping) } /// Append a [`syn::Type`], which has been grouping-normalised fn append_syn_type_inner( &mut self, te_span: Span, v: syn::Type, grouping: Grouping, ); /// Append using a function which generates tokens /// /// If you have an `impl `[`ToTokens`], /// use [`append_tokens`](ExpansionOutput::append_tokens) instead. /// /// Not supported within `${paste }`. /// The `NotInPaste` parameter makes this method unreachable /// when expanding within `${paste }`; /// or to put it another way, /// it ensures that such an attempt would have been rejected /// during template parsing. fn append_tokens_with( &mut self, np: &Self::NotInPaste, f: impl FnOnce(&mut TokenAccumulator) -> syn::Result<()>, ) -> syn::Result<()>; /// "Append" a substitution which can only be used within a boolean /// /// Such a thing cannot be expanded, so it cannot be appended, /// so this function must be unreachable. /// `expand_bool_only` is called (in expansion contexts) /// to handle uninhabited `SubstDetails` variants etc. /// /// Implementing it involves demonstrating that /// either `self`, or `Self::BoolOnly`, is uninhabited, /// with a call to [`void::unreachable`]. fn append_bool_only(&mut self, bool_only: &Self::BoolOnly) -> !; /// Note that an error occurred /// /// This must arrange to /// (eventually) convert it using `into_compile_error` /// and emit it somewhere appropriate. fn record_error(&mut self, err: syn::Error); /// Convenience method for noting an error with span and message fn write_error(&mut self, span: &S, message: M) { self.record_error(span.error(message)); } /// Convenience method for writing a `ToTokens` /// /// Dispatches to /// [`append_tokens_with`](ExpansionOutput::append_tokens_with) /// Not supported within `${paste }`. // // I experimented with unifying this with `append_tokens_with` // using a `ToTokensFallible` trait, but it broke type inference // rather badly and had other warts. fn append_tokens( &mut self, np: &Self::NotInPaste, tokens: impl ToTokens, ) -> syn::Result<()> { self.append_tokens_with(np, |out| { out.append(tokens); Ok(()) }) } /// Make a new empty expansion output, introduced at `kw_span` /// /// Normally, call sites use an inherent constructor method. /// This one is used for special cases, eg `${ignore ...}` fn new_with_span(kw_span: Span) -> Self; fn default_subst_meta_as(kw: Span) -> syn::Result>; /// Implement the core of the `ignore` keyword /// /// If there was an error, returns it. /// Otherwise, discards everything. fn ignore_impl(self) -> syn::Result<()>; /// Implement the `dbg` keyword /// /// Specifically: /// * Write the expansion of `child` to `msg` in human-readable form /// * Without a trailing newline /// * Subsume it into `self` /// /// Failures are to be reported, and subsumed into `self`. fn dbg_expand( &mut self, kw_span: Span, ctx: &Context, msg: &mut String, content: &Self::DbgContent, ); } /// Convenience trait providing `item.expand()` /// /// Implementations of this are often specific to the [`ExpansionOutput`]. /// /// Having this as a separate trait, /// rather than hanging it off `ExpansionOutput`, /// makes the expansion method more convenient to call. /// /// It also avoids having to make all of these expansion methods /// members of the `ExpansionOutput` trait. pub trait Expand { fn expand(&self, ctx: &Context, out: &mut O) -> syn::Result<()>; } /// Convenience trait providing `fn expand(self)`, infallible version /// /// Some of our `expand` functions always record errors /// within the output accumulator /// and therefore do not need to return them. pub trait ExpandInfallible { fn expand(&self, ctx: &Context, out: &mut O); } /// Accumulates tokens, or errors /// /// We collect all the errors, and if we get an error, don't write /// anything out. /// This is because `compile_error!` (from `into_compile_error`) /// only works in certain places in Rust syntax (!) #[derive(Debug)] pub struct TokenAccumulator(Result); impl<'c> Context<'c> { pub fn is_enum(&self) -> bool { matches!(self.top.data, syn::Data::Enum(_)) } /// Description of the whole expansion, suitable for `dbg` option, etc. pub fn expansion_description(&self) -> impl Display { let ident = &self.top.ident; if let Some(templ) = &self.template_name { format!( "derive-deftly expansion of {} for {}", templ.to_token_stream(), ident, ) } else { format!("derive-deftly expansion, for {}", ident,) } } } impl Default for TokenAccumulator { fn default() -> Self { TokenAccumulator(Ok(TokenStream::new())) } } impl TokenAccumulator { pub fn new() -> Self { Self::default() } pub fn with_tokens( &mut self, f: impl FnOnce(&mut TokenStream) -> R, ) -> Option { self.0.as_mut().ok().map(f) } pub fn append(&mut self, t: impl ToTokens) { self.with_tokens(|out| t.to_tokens(out)); } pub fn tokens(self) -> syn::Result { self.0 } /// Appends `val`, via [`ToTokensPunctComposable`] or [`ToTokens`] pub fn append_maybe_punct_composable( &mut self, val: &(impl ToTokens + ToTokensPunctComposable), composable: bool, ) { self.with_tokens(|out| { if composable { val.to_tokens_punct_composable(out); } else { val.to_tokens(out); } }); } } impl SubstParseContext for TokenAccumulator { type NotInPaste = (); type NotInBool = (); type DbgContent = Template; fn not_in_bool(_: &impl Spanned) -> syn::Result<()> { Ok(()) } fn not_in_paste(_: &impl Spanned) -> syn::Result<()> { Ok(()) } fn meta_recog_usage() -> meta::Usage { meta::Usage::Value } type BoolOnly = Void; type SpecialParseContext = (); } impl ExpansionOutput for TokenAccumulator { fn append_identfrag_toks( &mut self, ident: &I, ) -> Result<(), I::BadIdent> { self.with_tokens( |out| ident.frag_to_tokens(out), // ) .unwrap_or(Ok(())) } fn append_idpath( &mut self, _te_span: Span, pre: A, ident: &I, post: B, grouping: Grouping, ) -> Result<(), I::BadIdent> where A: FnOnce(&mut TokenAccumulator), B: FnOnce(&mut TokenAccumulator), I: IdentFrag, { let inner = match self.with_tokens(|_outer| { let mut inner = TokenAccumulator::new(); pre(&mut inner); inner.append_identfrag_toks(ident)?; post(&mut inner); Ok(inner) }) { None => return Ok(()), // earlier errors, didn't process Some(Err(e)) => return Err(e), Some(Ok(ta)) => ta, }; match inner.tokens() { Ok(ts) => self.append(grouping.surround(ts)), Err(e) => self.record_error(e), } Ok(()) } fn append_syn_litstr(&mut self, lit: &syn::LitStr) { self.append(lit); } fn append_syn_type_inner( &mut self, _te_span: Span, ty: syn::Type, grouping: Grouping, ) { self.append(grouping.surround(ty)); } fn append_tokens_with( &mut self, _not_in_paste: &(), f: impl FnOnce(&mut TokenAccumulator) -> syn::Result<()>, ) -> syn::Result<()> { f(self) } fn append_bool_only(&mut self, bool_only: &Self::BoolOnly) -> ! { void::unreachable(*bool_only) } fn record_error(&mut self, err: syn::Error) { if let Err(before) = &mut self.0 { before.combine(err); } else { self.0 = Err(err) } } fn new_with_span(_kw_span: Span) -> Self { Self::new() } fn default_subst_meta_as(kw: Span) -> syn::Result> { Err(kw.error("missing `as ...` in meta expansion")) } fn ignore_impl(self) -> syn::Result<()> { self.0.map(|_: TokenStream| ()) } fn dbg_expand( &mut self, _kw_span: Span, ctx: &Context, msg: &mut String, content: &Template, ) { let mut child = TokenAccumulator::new(); content.expand(ctx, &mut child); let child = child.tokens(); match &child { Err(e) => write!(msg, "/* ERROR: {} */", e), Ok(y) => write!(msg, "{}", y), } .expect("write! failed"); match child { Ok(y) => self.append(y), Err(e) => self.record_error(e), } } } work/macros/macros.rs0000664000000000000000000004707214763657303012076 0ustar #![allow(clippy::style, clippy::complexity)] #![doc=include_str!("README.md")] // // This is the actual proc-macro crate. // // All it exports (or can export) are the proc macros themselves. // Everything else that is `pub` could be written `pub(crate)`. mod prelude; pub(crate) use prelude::*; // Implementation - common parts #[macro_use] pub(crate) mod utils; #[macro_use] pub(crate) mod adviseable; pub(crate) mod framework; // Implementation - specific areas pub(crate) mod accum; pub(crate) mod approx_equal; pub(crate) mod boolean; pub(crate) mod dbg_allkw; pub(crate) mod expand; pub(crate) mod meta; pub(crate) mod options; pub(crate) mod paste; pub(crate) mod repeat; pub(crate) mod syntax; #[cfg_attr(not(feature = "beta"), path = "beta_disabled.rs")] pub(crate) mod beta; // Implementations of each proc-macros pub(crate) mod adhoc; pub(crate) mod define; pub(crate) mod derive; pub(crate) mod engine; pub(crate) mod semver; pub(crate) mod compat_syn_2; pub(crate) mod compat_syn_common; #[doc=include_str!("HACKING.md")] mod _doc_hacking {} #[doc=include_str!("NOTES.md")] mod _doc_notes {} /// Dummy of proc_macro for use when compiling outside of proc macro context #[cfg(not(proc_macro))] pub(crate) mod proc_macro { pub(crate) use proc_macro2::TokenStream; } //========== `expect`, the `check` module (or dummy version) ========== // "expect" feature; module named check.rs for tab completion reasons #[cfg(feature = "expect")] mod check; #[cfg(not(feature = "expect"))] mod check { use super::prelude::*; #[derive(Debug, Clone, Copy, PartialEq)] pub struct Target(Void); impl FromStr for Target { type Err = Void; fn from_str(_: &str) -> Result { panic!("output syntax checking not supported, enable `expect` feature of `derive-deftly`") } } pub fn check_expected_target_syntax( _ctx: &framework::Context, _output: &mut TokenStream, target: DdOptVal, ) { void::unreachable(target.value.0) } pub fn check_expect_opcontext( op: &DdOptVal, _context: OpContext, ) -> syn::Result<()> { void::unreachable(op.value.0) } } impl DdOptValDescribable for check::Target { const DESCRIPTION: &'static str = "expected output syntax (`expect` option)"; } //========== actual macro entrypoints ========== /// Wraps an actual macro implementation function that uses a proc_macro2 /// implementation to expose a proc_macro implementation instead. // // Clippy gives false positives for converting between proc_macro[2]::TokenStream. #[allow(clippy::useless_conversion)] fn wrap_macro_func( func: F, input: proc_macro::TokenStream, ) -> proc_macro::TokenStream where F: FnOnce( proc_macro2::TokenStream, ) -> Result, { let input = proc_macro2::TokenStream::from(input); let output = func(input).unwrap_or_else(|e| e.into_compile_error()); proc_macro::TokenStream::from(output) } /// Template expansion engine, internal /// /// /// /// /// Normally you do not need to mention this macro. /// /// derive-deftly does its work by /// (defining and then) invoking various interrelated macros /// including `macro_rules` macros and proc macros. /// These ultimately end up calling this macro, /// which takes a template and a data structure, /// and expands the template for that data structure. /// /// This macro's behvaiour is not currently stable or documented. /// If you invoke it yourself, you get to keep all the pieces. #[cfg_attr(proc_macro, proc_macro)] pub fn derive_deftly_engine( input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { wrap_macro_func(engine::derive_deftly_engine_func_macro, input) } /// Expand an ad-hoc template, on a data structure decorated `#[derive_deftly_adhoc]` /// /// /// /// /// ``` // We're in the macro crate, where the facade crate is not available. // So we must do some namespace-swizzling. /// # use derive_deftly_macros as derive_deftly; // `proc-macro-crate` says `Itself` so generates ::derive_deftly_engine, // which is wrong for a doctest. Fudge that. We must also make sure // we're not inside main here, so we must define a main. /// # use derive_deftly::derive_deftly_engine; /// # fn main(){} /// use derive_deftly::{Deftly, derive_deftly_adhoc}; /// #[derive(Deftly)] /// #[derive_deftly_adhoc] /// struct DdtaStructureType { } /// // Smoke and mirrors so we can use metasyntactic OPTIONS and TEMPLATE. /// # macro_rules! derive_deftly_adhoc { { /// # $x:ident OPTIONS,..: TEMPLATE /// # } => { derive_deftly_macros::derive_deftly_adhoc! { /// # $x expect items: fn x(){} /// # } } } /// derive_deftly_adhoc! { /// DdtaStructureType OPTIONS,..: /// TEMPLATE /// } /// ``` /// /// Expands the template `TEMPLATE` for the type `DdtaStructureType`, /// /// `OPTIONS,..` is an optional comma-separated list of /// [expansion options](doc_reference/index.html#expansion-options). /// /// The definition of `DdtaStructureType` must have been decorated /// with [`#[derive(Deftly)]`](crate::Deftly), /// and `#[derive_deftly_adhoc]`, /// and the resulting `derive_deftly_driver_TYPE` macro must be /// available in scope. /// /// `derive_deftly_adhoc!` can be used in any context /// where the Rust language permits macro calls. /// For example, it can expand to expressions, statements, /// types, or patterns. #[cfg_attr(proc_macro, proc_macro)] pub fn derive_deftly_adhoc( input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { wrap_macro_func(adhoc::derive_deftly_adhoc, input) } /// Define a reuseable template /// /// /// /// /// ```text /// define_derive_deftly! { /// [/// DOCS] /// [export] MyMacro OPTIONS,..: /// TEMPLATE /// } /// ``` /// /// Then, `MyMacro` can be used with /// [`#[derive(Deftly)]`](crate::Deftly) /// `#[derive_deftly(MyMacro)]`. /// /// `OPTIONS,..` /// is an optional comma-separated list of /// [expansion options](doc_reference/index.html#expansion-options), /// which will be applied whenever this template is expanded. /// /// `DOCS`, /// if supplied, are used as the rustdocs /// for the captured template macro `derive_deftly_template_MyMacro`. /// derive-deftly will then also append a note about /// how to invoke the template. /// /// ## Template definition macro `derive_deftly_template_MyMacro` /// /// The template is made into a `macro_rules` macro /// named `derive_deftly_template_MyMacro`, /// which is referenced when the template is applied. /// /// The template definition macro /// from `define_derive_deftly!` /// must be in scope at the point where you try to use it /// (with `#[derive(Deftly)] #[derive_deftly(MyMacro)]`). /// If the template definition is in another module, /// you may need to annotate that module with `#[macro_use]`. /// See the /// [documentation for `#[derive(Deftly)]`](derive.Deftly.html#scoping-and-ordering-within-the-same-crate). /// /// ## Exporting a template for use by other crates /// /// With `export MyMacro`, `define_derive_deftly!` exports the template /// for use by other crates. /// Then, it is referred to in other crates /// with `#[derive_ahdoc(this_crate::MyMacro)]`. /// /// I.e., `export MyMacro` causes the `derive_deftly_template_MyMacro` /// pattern macro to be exported with `#[macro_export]`. /// /// Note that a template is always exported at the crate top level, /// not in a sub-module, /// even if it is *defined* in a sub-module. /// Also, note that `export` does not have any effect on /// visibility of the template *within the same crate*. /// You may still need `#[macro_use]`. /// /// ### You must re-export `derive_deftly`; semver implications /// /// When exporting a template to other crates, you must also /// re-export `derive_deftly`, /// at the top level of your crate: /// /// ```ignore /// #[doc(hidden)] /// pub use derive_deftly; /// ``` /// This is used to find the template expansion engine, /// and will arrange that your template is expanded /// by the right version of derive-deftly. /// The template syntax is that for *your* version of `derive-deftly`, /// even if the depending crate uses a different version of derive-deftly. /// /// You should *not* treat a breaking change /// to derive-deftly's template syntax /// (which is a major change to derive-deftly), /// nor a requirement to use a newer template feature, /// as a breaking changes in the API of your crate. /// (You *should* use `#[doc(hidden)]`, or other approaches, /// to discourage downstream crates from using /// the derive-deftly version you re-export. /// Such use would be outside the semver guarantees.) /// /// You *should* call /// [`derive_deftly::template_export_semver_check!`](macro@template_export_semver_check) /// once in each crate that exports macros. /// This will notify you, by breaking your build, /// if you update to a derive-deftly version /// that has semver implications for other crates that use your macros. /// /// Changes that would require a semver bump /// for all libraries that export templates, /// will be rare, and specially marked in the derive-deftly changelog. /// Search for sections with titles containing "template export semver". /// /// ## Namespacing within a template /// /// Within the template, /// items within your crate can be referred to with /// [`$crate`](doc_reference/index.html#x:crate). /// /// For other items, /// including from the standard library e.g., `std::option::Option`, /// you may rely on the context which uses the template /// to have a reasonable namespace, /// or use a explicit paths starting with `std` or `::std` or `::core` /// or `$crate` (perhaps naming a re-export). /// /// Overall, the situation is similar to defining /// an exported `macro_rules` macro. #[cfg_attr(proc_macro, proc_macro)] pub fn define_derive_deftly( input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { wrap_macro_func(define::define_derive_deftly_func_macro, input) } /// Perform ad-hoc templating driven by a data structure /// /// /// /// /// This macro does two things: /// /// 1. If `#[derive_deftly(MyMacro)]` attributes are also specified, /// they are taken to refer to reuseable templates /// defined with /// [`define_derive_deftly!`](macro@crate::define_derive_deftly). /// Each such `MyMacro` is applied to the data structure. /// /// You can specify /// [expansion options](doc_reference/index.html#expansion-options) /// for each such template application, by writing /// `#[derive_deftly(MyMacro[OPTIONS,..])]`, where /// `[OPTIONS,..]` is a comma-separated list of expansion options /// contained within `[ ]`. /// /// 2. If `#[derive_deftly_adhoc]` is specified, /// captures the data structure definition, /// so that it can be used with calls to /// [`derive_deftly_adhoc!`](macro@crate::derive_deftly_adhoc). /// /// ## `#[deftly]` attribute /// /// The contents of `#[deftly]` attributes are made available /// to templates via the /// [`${Xmeta}`](doc_reference/index.html#tmeta-vmeta-fmeta--deftly-attributes) /// expansions. /// /// If none of the template(s) recognise them, /// [it is an error](doc_reference/index.html#unrecognisedunused-deftly-attributes), /// (unless `#[derive_deftly_adhoc]` is specified). /// /// `derive-deftly` /// [does not impose any namespacing](doc_reference/index.html#attribute-namespacing) /// within `#[deftly]`: /// /// ## Scoping and ordering within the same crate /// /// **Summary of required ordering** /// /// 1. `define_derive_deftly! { MyMacro = ... }` /// 2. `#[derive(Deftly)] #[derive_deftly(MyMacro)] struct MyStruct { ... }` /// 3. `derive_deftly_adhoc! { MyStruct: ... }` /// /// Any reusable templates defined with /// `define_derive_deftly!` must lexically their precede /// uses with `#[derive(Deftly) #[derive_deftly(...)]`. /// /// And, for one-off templates (`derive_deftly_adhoc!`), /// the data structure with its `#[derive(Deftly)]` /// must lexically precede /// the references in `derive_deftly_adhoc!`, /// so that the data structure definition macro /// is in scope. /// /// In each case, /// if the definition is in another module /// in the same crate, /// the defining module's `mod` statement must come before /// the reference, /// and /// the `mod` statement will need `#[macro_use]`. /// So the placement and order of `mod` statements can matter. /// Alternatively, it is possible to use path-based scoping; /// there is /// [an example in the Guide](https://diziet.pages.torproject.net/rust-derive-deftly/latest/guide/templates-in-modules.html#path-scope). /// /// ## Applying a template (derive-deftly macro) from another crate /// /// `#[derive_deftly(some_crate::MyMacro)]` /// applies an exported template /// defined and exported by `some_crate`. /// /// You can import a template from another crate, /// so you can apply it with an unqualified name, /// with `use`, /// but the `use` must refer to /// the actual pattern macro name `derive_deftly_template_MyMacro`: /// ``` // See the doc comment for `derive_deftly_adhoc`. /// # use derive_deftly_macros as derive_deftly; /// # use derive_deftly::derive_deftly_engine; /// # fn main(){} // We can't make another crate. Fake up the macro definition /// # derive_deftly::define_derive_deftly! { TheirMacro: } /// use derive_deftly::Deftly; // and don't really try to import it, then /// # #[cfg(any())] /// use other_crate::derive_deftly_template_TheirMacro; /// #[derive(Deftly)] /// #[derive_deftly(TheirMacro)] /// struct MyStruct { // ... /// # } /// ``` /// /// ## Captured data structure definition `derive_deftly_driver_TYPE` /// /// With `#[derive_deftly_adhoc]`, /// the data structure is captured /// for use by /// [`derive_deftly_adhoc!`](macro@crate::derive_deftly_adhoc). /// /// Specifically, by defining /// a `macro_rules` macro called `derive_deftly_driver_TYPE`, /// where `TYPE` is the name of the type /// that `#[derive(Deftly)]` is applied to. /// /// /// /// ### Exporting the driver for downstream crates' templates /// // Really, the documentation about this in `pub-a.rs` and `pub-b.rs`, // should be somewhere in our rustdoc output. // But I don't want to put it *here* because it would completely // dominate this macro documentation. // So for now just reference the source tree docs. // (We can't really easily provide even a link.) // I think this is such a minority feature, // that hiding the docs like this is OK. // /// To cause the macro embodying the driver struct to be exported, /// write: /// `#[derive_deftly_adhoc(export)]`. /// The driver can then be derived from in other crates, /// with `derive_deftly_adhoc! { exporting_crate::DriverStruct: ... }`. /// /// #### Semver hazards /// /// This is a tricky feature, /// which should only be used by experts /// who fully understand the implications. /// It effectively turns the body of the struct into a macro, /// with a brittle API /// and very limited support for namespacing or hygiene. /// /// See `pub mod a_driver` in the example file `pub-a.rs`, /// in the source tree, /// for a fuller discussion of the implications, /// and some advice. /// /// If you do this, you must **pin your derive-deftly** to a minor version, /// as you may need to treat *minor* version updates in derive-deftly /// as semver breaks for your crate. /// And every time you update, you must read the `CHANGELOG.md`, /// since there is nothing that will warn you automatically /// about breaking changes. // // This is the implementation of #[derive(Deftly)] #[cfg_attr( proc_macro, proc_macro_derive( Deftly, attributes(deftly, derive_deftly, derive_deftly_adhoc) ) )] pub fn derive_deftly( input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { wrap_macro_func(derive::derive_deftly, input) } /// Check semver compatibility, for a crate which exports macros /// /// /// /// /// Causes a compilation error /// if and only if the specified version of `derive-deftly` /// is prior to the last *relevant change*, /// compared to the currently-running one. /// /// A *relevant change* is one which has semver implications /// for the API of a crate which exports derive-deftly templates. /// /// ## When and how to call this /// /// If you export templates, with `define_derive_deftly! { export ... }`, /// call this macro too, once in your crate. /// /// Pass it the version of `derive-deftly` that was current, /// when you last read the `derive-deftly` changelog /// and considered breaking changes. /// /// (The argument must be a string literal, containing a /// 2- or 3-element version number. /// If the 3rd element is omitted, 0 is used.) /// /// ## Guarantee /// /// You can upgrade your derive-deftly version, /// even across a semver-breaking change to derive-deftly, /// without making any consequential update to your crate's own semver. /// /// If a new version of derive-adhoc means *your* crate's /// API has semver-relevant changes, this macro will throw an error. /// (Of course that will only happen across semver-breaking /// updates of derive-deftly.) /// /// (Exporting a *driver* struct for derivation in downstream crates, /// `#[derive_deftly_adhoc(export)]`, is not covered by this promise.) /// /// ## Example /// /// ``` /// # use derive_deftly_macros as derive_deftly; /// derive_deftly::template_export_semver_check!("0.13.0"); /// ``` #[cfg_attr(proc_macro, proc_macro)] pub fn template_export_semver_check( input: proc_macro::TokenStream, ) -> proc_macro::TokenStream { wrap_macro_func(semver::template_export_semver_check_func_macro, input) } work/macros/meta.rs0000664000000000000000000015246014763657303011536 0ustar //! `#[deftly(...)]` meta attributes //! //! # Used meta checking //! //! Most of this file is concerned with generating //! accurate and useful messages //! when a driver is decorated with `#[deftly(...)]` attributes //! which are not used by any template. //! //! We distinguish "used" metas from "recognised" ones. //! //! "Used" ones are those actually tested, and used, //! during the dynamic expansion of the template. //! They are recorded in the [`PreprocessedMetas`], //! which contains a `Cell` for each supplied node. //! //! "Recognised" ones are those which appear anywhere in the template. //! These are represented in a data structure [``Recognised`]. //! This is calculated by scanning the template, //! using the `FindRecogMetas` trait. //! //! Both of these sets are threaded through //! the ACCUM data in successive template expansions; //! in the final call (`EngineFinalInput`), //! they are combined together, //! and the driver's metas are checked against them. use super::framework::*; use indexmap::IndexMap; use Usage as U; //---------- common definitions ---------- /// Indicates one of `fmeta`, `vmeta` or `tmeta` #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] #[derive(AsRefStr, EnumString, EnumIter)] #[rustfmt::skip] pub enum Scope { // NB these keywords are duplicated in SubstDetails #[strum(serialize = "tmeta")] T, #[strum(serialize = "vmeta")] V, #[strum(serialize = "fmeta")] F, } /// Scope of a *supplied* meta (`#[deftly(...)]`) attribute /// /// Also encodes, for metas at the toplevel, /// whether it's a struct or an enum. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] // #[derive(strum::Display, EnumIter)] #[strum(serialize_all = "snake_case")] pub enum SuppliedScope { Struct, Enum, Variant, Field, } impl SuppliedScope { fn recog_search(self) -> impl Iterator { use Scope as S; use SuppliedScope as SS; match self { SS::Struct => &[S::T, S::V] as &[_], SS::Enum => &[S::T], SS::Variant => &[S::V], SS::Field => &[S::F], } .iter() .copied() } } /// `(foo(bar))` in eg `fmeta(foo(bar))` /// /// includes the parens #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Label { // Nonempty list, each with nonempty segments. // Outermost first. pub lpaths: Vec, } /// Meta designator eg `fmeta(foo(bar))` // Field order must be the same as BorrowedDesig #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct Desig { pub scope: Scope, pub label: Label, } #[derive(Hash)] // Field order must be the same as meta::Desig struct BorrowedDesig<'p> { pub scope: Scope, pub lpaths: &'p [&'p syn::Path], } //---------- substitutions in a template ---------- #[derive(Debug)] pub struct SubstMeta { pub desig: Desig, pub as_: Option>, pub default: Option<(Argument, O::NotInBool, beta::Enabled)>, } #[derive(Debug, Clone, AsRefStr, Display)] #[allow(non_camel_case_types)] // clearer to use the exact ident pub enum SubstAs { expr(O::NotInBool, O::NotInPaste, SubstAsSupported), ident(O::NotInBool), items(O::NotInBool, O::NotInPaste, SubstAsSupported), path(O::NotInBool), str(O::NotInBool), token_stream(O::NotInBool, O::NotInPaste), ty(O::NotInBool), } //---------- meta attrs in a driver ---------- /// A part like `(foo,bar(baz),zonk="value")` #[derive(Debug)] pub struct PreprocessedValueList { pub content: Punctuated, } /// `#[deftly(...)]` helper attributes pub type PreprocessedMetas = Vec; /// An `#[deftly()]` attribute, or a sub-tree within one /// /// Has interior mutability, for tracking whether the value is used. /// (So should ideally not be Clone, to help avoid aliasing bugs.) #[derive(Debug)] pub struct PreprocessedTree { pub path: syn::Path, pub value: PreprocessedValue, pub used: Cell>, } /// Content of a meta attribute /// /// Examples in doc comments are for /// `PreprocessedMeta.path` of `foo`, /// ie the examples are for `#[deftly(foo ..)]`. #[derive(Debug)] pub enum PreprocessedValue { /// `#[deftly(foo)]` Unit, /// `#[deftly(foo = "lit")]` Value { value: syn::Lit }, /// `#[deftly(foo(...))]` List(PreprocessedValueList), } //---------- search and match results ---------- /// Node in tree structure found in driver `#[deftly(some(thing))]` #[derive(Debug)] pub struct FoundNode<'l> { kind: FoundNodeKind<'l>, path_span: Span, ptree: &'l PreprocessedTree, } /// Node in tree structure found in driver `#[deftly(some(thing))]` #[derive(Debug)] pub enum FoundNodeKind<'l> { Unit, Lit(&'l syn::Lit), } /// Information about a nearby meta node we found /// /// "Nearby" means that the node we found is a prefix (in tree descent) /// of the one we were looking for, or vice versa. #[derive(Debug)] pub struct FoundNearbyNode<'l> { pub kind: FoundNearbyNodeKind, /// Span of the identifier in the actual `#[deftly]` driver attribute pub path_span: Span, pub ptree: &'l PreprocessedTree, } /// How the nearby node relates to the one we were looking for #[derive(Debug)] pub enum FoundNearbyNodeKind { /// We were looking to go deeper, but found a unit in `#[deftly]` Unit, /// We were looking to go deeper, but found a `name = value` in `#[deftly]` Lit, /// We were looking for a leaf, but we found nested list in `#[deftly]` List, } pub use FoundNearbyNodeKind as FNNK; pub use FoundNodeKind as FNK; //---------- meta attr enumeration and checking ---------- /// Whether a meta node was used (or ought to be used) #[derive(Debug, Copy, Clone, Hash, PartialOrd, Ord, Eq, PartialEq)] // #[derive(EnumIter)] pub enum Usage { BoolOnly, Value, } /// One lot of used metas in accumulation - argument to a `_meta_used` accum #[derive(Debug)] pub struct UsedGroup { pub content: TokenStream, } /// Something representing possibly checking that meta attributes are used #[derive(Debug, Clone)] pub enum CheckUsed { /// Yes, check them, by/with/from/to `T` Check(T), /// No, don't check them. Unchecked, } /// Information for meta checking, found in accumulation #[derive(Debug, Default)] pub struct Accum { pub recog: Recognised, pub used: Vec, } #[derive(Default, Debug, Clone)] pub struct Recognised { map: IndexMap, } pub trait FindRecogMetas { /// Search for `fmeta(..)` etc. expansions /// /// Add to `acc` any that are /// (recusively) within `self`, syntactically, fn find_recog_metas(&self, acc: &mut Recognised); } //==================== implementations) ==================== //---------- template parsing ---------- impl SubstMeta { fn span_whole(&self, scope_span: Span) -> Span { spans_join(chain!( [scope_span], // self.desig.label.spans(), )) .unwrap() } } impl Label { /// Nonempty pub fn spans(&self) -> impl Iterator + '_ { self.lpaths.iter().map(|path| path.span()) } } impl SubstAs { fn parse(input: ParseStream, nb: O::NotInBool) -> syn::Result { let kw: IdentAny = input.parse()?; let from_sma = |sma: SubstAs<_>| Ok(sma); // See keyword_general! in utils.rs macro_rules! keyword { { $($args:tt)* } => { keyword_general! { kw from_sma SubstAs; $($args)* } } } let not_in_paste = || O::not_in_paste(&kw); fn supported

(kw: &IdentAny) -> syn::Result> where P: SubstAsSupportStatus, { SubstAsSupportStatus::new(&kw) } keyword! { expr(nb, not_in_paste()?, supported(&kw)?) } keyword! { ident(nb) } keyword! { items(nb, not_in_paste()?, supported(&kw)?) } keyword! { path(nb) } keyword! { str(nb) } keyword! { token_stream(nb, not_in_paste()?) } keyword! { ty(nb) } Err(kw.error("unknown derive-deftly 'as' syntax type keyword")) } } impl SubstMeta { pub fn parse( input: ParseStream, kw_span: Span, scope: Scope, ) -> syn::Result { if input.is_empty() { O::missing_keyword_arguments(kw_span)?; } let label: Label = input.parse()?; fn store( kw: Span, already: &mut Option<(Span, V)>, call: impl FnOnce() -> syn::Result, ) -> syn::Result<()> { if let Some((already, _)) = already { return Err([(*already, "first"), (kw, "second")] .error("`${Xmeta ..}` option repeated")); } let v = call()?; *already = Some((kw, v)); Ok(()) } let mut as_ = None::<(Span, SubstAs)>; let mut default = None; while !O::IS_BOOL && !input.is_empty() { let keyword = Ident::parse_any(input)?; let kw_span = keyword.span(); let nb = O::not_in_bool(&kw_span).expect("checked already"); let ue = || beta::Enabled::new_for_syntax(kw_span); if keyword == "as" { store(kw_span, &mut as_, || SubstAs::parse(input, nb))?; } else if keyword == "default" { store(kw_span, &mut default, || { Ok((input.parse()?, nb, ue()?)) })?; } else { return Err(keyword.error("unknown option in `${Xmeta }`")); } if input.is_empty() { break; } let _: Token![,] = input.parse()?; } macro_rules! ret { { $( $f:ident )* } => { SubstMeta { desig: Desig { label, scope }, $( $f: $f.map(|(_span, v)| v), )* } } } Ok(ret! { as_ default }) } } //---------- driver parsing ---------- impl PreprocessedValueList { fn parse_outer(input: ParseStream) -> syn::Result { let meta; let _paren = parenthesized!(meta in input); Self::parse_inner(&meta) } } impl PreprocessedValueList { pub fn parse_inner(input: ParseStream) -> syn::Result { let content = Punctuated::parse_terminated(input)?; Ok(PreprocessedValueList { content }) } } impl Parse for PreprocessedTree { fn parse(input: ParseStream) -> syn::Result { use PreprocessedValue as PV; let path = input.call(syn::Path::parse_mod_style)?; let la = input.lookahead1(); let value = if la.peek(Token![=]) { let _: Token![=] = input.parse()?; let value = input.parse()?; PV::Value { value } } else if la.peek(token::Paren) { let list = input.call(PreprocessedValueList::parse_outer)?; PV::List(list) } else if la.peek(Token![,]) || input.is_empty() { PV::Unit } else { return Err(la.error()); }; let used = None.into(); // will be filled in later Ok(PreprocessedTree { path, value, used }) } } impl Parse for Label { fn parse(outer: ParseStream) -> syn::Result { fn recurse( lpaths: &mut Vec, outer: ParseStream, ) -> syn::Result<()> { let input; let paren = parenthesized!(input in outer); let path = input.call(syn::Path::parse_mod_style)?; if path.segments.is_empty() { return Err(paren .span .error("`deftly` attribute must have nonempty path")); } lpaths.push(path); if !input.is_empty() { recurse(lpaths, &input)?; } Ok(()) } let mut lpaths = vec![]; recurse(&mut lpaths, outer)?; Ok(Label { lpaths }) } } //---------- searching and matching ---------- impl Label { /// Caller must note meta attrs that end up being used! pub fn search<'a, F, G, E>( &self, pmetas: &'a [PreprocessedValueList], f: &mut F, g: &mut G, ) -> Result<(), E> where F: FnMut(FoundNode<'a>) -> Result<(), E>, G: FnMut(FoundNearbyNode<'a>) -> Result<(), E>, { for m in pmetas { for l in &m.content { Self::search_1(&self.lpaths, l, &mut *f, &mut *g)?; } } Ok(()) } fn search_1<'a, E, F, G>( // Nonempty lpaths: &[syn::Path], ptree: &'a PreprocessedTree, f: &mut F, g: &mut G, ) -> Result<(), E> where F: FnMut(FoundNode<'a>) -> Result<(), E>, G: FnMut(FoundNearbyNode<'a>) -> Result<(), E>, { use PreprocessedValue as PV; if ptree.path != lpaths[0] { return Ok(()); } let path_span = ptree.path.span(); let mut nearby = |kind| { g(FoundNearbyNode { kind, path_span, ptree, }) }; let deeper = if lpaths.len() <= 1 { None } else { Some(&lpaths[1..]) }; match (deeper, &ptree.value) { (None, PV::Unit) => f(FoundNode { path_span, kind: FNK::Unit, ptree, })?, (None, PV::List(_)) => nearby(FNNK::List)?, (None, PV::Value { value, .. }) => f(FoundNode { path_span, kind: FNK::Lit(value), ptree, })?, (Some(_), PV::Value { .. }) => nearby(FNNK::Lit)?, (Some(_), PV::Unit) => nearby(FNNK::Unit)?, (Some(d), PV::List(l)) => { for m in l.content.iter() { Self::search_1(d, m, &mut *f, &mut *g)?; } } } Ok(()) } } impl Label { pub fn search_eval_bool( &self, pmetas: &PreprocessedMetas, ) -> Result<(), Found> { let found = |ptree: &PreprocessedTree| { ptree.update_used(Usage::BoolOnly); Err(Found) }; self.search( pmetas, &mut |av| /* got it! */ found(av.ptree), &mut |nearby| match nearby.kind { FNNK::List => found(nearby.ptree), FNNK::Unit => Ok(()), FNNK::Lit => Ok(()), }, ) } } //---------- scope and designator handling ---------- impl SubstMeta where O: SubstParseContext, { pub fn repeat_over(&self) -> Option { match self.desig.scope { Scope::T => None, Scope::V => Some(RO::Variants), Scope::F => Some(RO::Fields), } } } impl SubstMeta where O: SubstParseContext, { pub fn pmetas<'c>( &self, ctx: &'c Context<'c>, kw_span: Span, ) -> syn::Result<&'c PreprocessedMetas> { Ok(match self.desig.scope { Scope::T => &ctx.pmetas, Scope::V => &ctx.variant(&kw_span)?.pmetas, Scope::F => &ctx.field(&kw_span)?.pfield.pmetas, }) } } impl ToTokens for Label { fn to_tokens(&self, out: &mut TokenStream) { let mut lpaths = self.lpaths.iter().rev(); let mut current = lpaths.next().expect("empty path!").to_token_stream(); let r = loop { let span = current.span(); let mut group = proc_macro2::Group::new(Delimiter::Parenthesis, current); group.set_span(span); let wrap = if let Some(y) = lpaths.next() { y } else { break group; }; current = quote! { #wrap #group }; }; r.to_tokens(out); } } impl Desig { fn to_tokens(&self, scope_span: Span, out: &mut TokenStream) { let scope: &str = self.scope.as_ref(); Ident::new(scope, scope_span).to_tokens(out); self.label.to_tokens(out); } } impl Parse for Desig { fn parse(input: ParseStream) -> syn::Result { let scope: syn::Ident = input.parse()?; let scope = scope .to_string() .parse() .map_err(|_| scope.error("invalid meta keyword/level"))?; let label = input.parse()?; Ok(Self { scope, label }) } } impl indexmap::Equivalent for BorrowedDesig<'_> { fn equivalent(&self, desig: &Desig) -> bool { let BorrowedDesig { scope, lpaths } = self; *scope == desig.scope && itertools::equal(lpaths.iter().copied(), &desig.label.lpaths) } } /// `Display`s as a `#[deftly(...)]` as the user might write it struct DisplayAsIfSpecified<'r> { lpaths: &'r [&'r syn::Path], /// Included after the innermost lpath, inside the parens inside_after: &'r str, } impl Display for DisplayAsIfSpecified<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "#[deftly")?; for p in self.lpaths { write!(f, "({}", p.to_token_stream())?; } write!(f, "{}", self.inside_after)?; for _ in self.lpaths { write!(f, ")")?; } Ok(()) } } // Tests that our `BorrowedDesig` `equivalent` impl is justified. #[test] fn check_borrowed_desig() { use super::*; use indexmap::Equivalent; use itertools::iproduct; use std::hash::{Hash, Hasher}; #[derive(PartialEq, Eq, Debug, Default)] struct TrackingHasher(Vec>); impl Hasher for TrackingHasher { fn write(&mut self, bytes: &[u8]) { self.0.push(bytes.to_owned()); } fn finish(&self) -> u64 { unreachable!() } } impl TrackingHasher { fn hash(t: impl Hash) -> Self { let mut self_ = Self::default(); t.hash(&mut self_); self_ } } type Case = (Scope, &'static [&'static str]); const TEST_CASES: &[Case] = &[ (Scope::T, &["path"]), (Scope::T, &["r#path"]), (Scope::V, &["path", "some::path"]), (Scope::V, &["r#struct", "with_generics::<()>"]), (Scope::F, &[]), // illegal Desig, but test anyway ]; struct Desigs<'b> { owned: Desig, borrowed: BorrowedDesig<'b>, } impl Desigs<'_> { fn with((scope, lpaths): &Case, f: impl FnOnce(Desigs<'_>)) { let scope = *scope; let lpaths = lpaths .iter() .map(|l| syn::parse_str(l).expect(l)) .collect_vec(); let owned = { let label = Label { lpaths: lpaths.clone(), }; Desig { scope, label } }; let lpaths_borrowed; let borrowed = { lpaths_borrowed = lpaths.iter().collect_vec(); BorrowedDesig { scope, lpaths: &*lpaths_borrowed, } }; f(Desigs { owned, borrowed }) } } // Test that for each entry in TEST_CASES, when parsed into Paths, etc., // BorrowedDesig is `equivalent` to, and hashes the same as, Desig. for case in TEST_CASES { Desigs::with(case, |d| { assert!(d.borrowed.equivalent(&d.owned)); assert_eq!( TrackingHasher::hash(&d.owned), TrackingHasher::hash(&d.borrowed), ); }); } // Compare every TEST_CASES entry with every other entry. // See if the owned forms are equal (according to `PartialEq`). // Insist that the Borrowed vs owned `equivalent` relation agrees, // in both directions. // And, if they are equal, insist that the hashes all agree. for (case0, case1) in iproduct!(TEST_CASES, TEST_CASES) { Desigs::with(case0, |d0| { Desigs::with(case1, |d1| { let equal = d0.owned == d1.owned; assert_eq!(equal, d0.borrowed.equivalent(&d1.owned)); assert_eq!(equal, d1.borrowed.equivalent(&d0.owned)); if equal { let hash = TrackingHasher::hash(&d0.owned); assert_eq!(hash, TrackingHasher::hash(&d1.owned)); assert_eq!(hash, TrackingHasher::hash(&d0.borrowed)); assert_eq!(hash, TrackingHasher::hash(&d1.borrowed)); } }); }); } } //---------- conditional support for `Xmeta as items` ---------- #[cfg(feature = "meta-as-expr")] pub type ValueExpr = syn::Expr; #[cfg(not(feature = "meta-as-expr"))] pub type ValueExpr = MetaUnsupported; #[cfg(feature = "meta-as-items")] pub type ValueItems = Concatenated; #[cfg(not(feature = "meta-as-items"))] pub type ValueItems = MetaUnsupported; /// newtype to avoid coherence - it doesn't impl `Parse + ToTokens` #[derive(Debug, Copy, Clone)] pub struct MetaUnsupported(Void); #[derive(Debug, Copy, Clone)] pub struct SubstAsSupported(P::Marker); /// Implemented for syn types supported in this build, and `MetaUnsupported` pub trait SubstAsSupportStatus: Sized { type Marker; type Parsed: Parse + ToTokens; fn new(kw: &IdentAny) -> syn::Result>; } impl SubstAsSupported

{ fn infer_type(&self, _parsed: &P::Parsed) {} } impl SubstAsSupportStatus for T { type Marker = (); type Parsed = T; fn new(_kw: &IdentAny) -> syn::Result> { Ok(SubstAsSupported(())) } } impl SubstAsSupportStatus for MetaUnsupported { type Marker = MetaUnsupported; type Parsed = TokenStream; fn new(kw: &IdentAny) -> syn::Result> { Err(kw.error(format_args!( // We're a bit fast and loose here: if kw contained `_`, // or there were aliases, this message would be a bit wrong. "${{Xmeta as {}}} used but cargo feature meta-as-{} disabled", **kw, **kw, ))) } } impl ToTokens for MetaUnsupported { fn to_tokens(&self, _out: &mut TokenStream) { void::unreachable(self.0) } } //---------- template expansion ---------- impl SubstMeta where O: ExpansionOutput, TemplateElement: Expand, { pub fn expand( &self, ctx: &Context, kw_span: Span, out: &mut O, pmetas: &PreprocessedMetas, ) -> syn::Result<()> { let SubstMeta { desig, as_, default, } = self; let mut found = None::; let mut hint = None::; let span_whole = self.span_whole(kw_span); let self_loc = || (span_whole, "expansion"); let error_loc = || [ctx.error_loc(), self_loc()]; desig.label.search( pmetas, &mut |av: FoundNode| { if let Some(first) = &found { return Err([(first.path_span, "first occurrence"), (av.path_span, "second occurrence"), self_loc()].error( "tried to expand just attribute value, but it was specified multiple times" )); } found = Some(av); Ok(()) }, &mut |nearby| { hint.get_or_insert(nearby); Ok(()) }, )?; if let (None, Some((def, ..))) = (&found, default) { return Ok(def.expand(ctx, out)); } let found = found.ok_or_else(|| { if let Some(hint) = hint { let hint_msg = match hint.kind { FNNK::Unit => "expected a list with sub-attributes, found a unit", FNNK::Lit => "expected a list with sub-attributes, found a simple value", FNNK::List => "expected a leaf node, found a list with sub-attributes", }; let mut err = hint.path_span.error(hint_msg); err.combine(error_loc().error( "attribute value expanded, but no suitable value in data structure definition" )); err } else { error_loc().error( "attribute value expanded, but no value in data structure definition" ) } })?; found.ptree.update_used(Usage::Value); found.expand(span_whole, as_, out)?; Ok(()) } } fn metavalue_spans(tspan: Span, vspan: Span) -> [ErrorLoc<'static>; 2] { [(vspan, "attribute value"), (tspan, "template")] } /// Obtain the `LiStr` from a meta node value (ie, a `Lit`) /// /// This is the thing we actually use. /// Non-string-literal values are not allowed. fn metavalue_litstr<'l>( lit: &'l syn::Lit, tspan: Span, msg: fmt::Arguments<'_>, ) -> syn::Result<&'l syn::LitStr> { match lit { syn::Lit::Str(s) => Ok(s), // having checked derive_builder, it doesn't handle // Lit::Verbatim so I guess we don't need to either. _ => Err(metavalue_spans(tspan, lit.span()).error(msg)), } } /// Convert a literal found in a meta item into `T` /// /// `into_what` is used only for error reporting pub fn metavalue_lit_as( lit: &syn::Lit, tspan: Span, into_what: &dyn Display, ) -> syn::Result where T: Parse + ToTokens, { let s = metavalue_litstr( lit, tspan, format_args!( "expected string literal, for conversion to {}", into_what, ), )?; let t: TokenStream = s.parse().map_err(|e| { // Empirically, parsing a LitStr in actual proc macro context, with // proc_macro2, into tokens, can generate a lexical error with a // "fallback" Span. Then, attempting to render the results, // including the eventual compiler_error! invocation, back to // a compiler proc_ma cor::TokenStream can panic with // "compiler/fallback mismatch". // // https://github.com/dtolnay/syn/issues/1504 // // Attempt to detect this situation. match (|| { let _: String = (&e).into_iter().next()?.span().source_text()?; Some(()) })() { Some(()) => e, None => lit.span().error(e.to_string()), } })?; let thing: T = syn::parse2(t)?; Ok(thing) } impl<'l> FoundNode<'l> { fn expand( &self, tspan: Span, as_: &Option>, out: &mut O, ) -> syn::Result<()> where O: ExpansionOutput, { let spans = |vspan| metavalue_spans(tspan, vspan); let lit = match self.kind { FNK::Unit => return Err(spans(self.path_span).error( "tried to expand attribute which is just a unit, not a literal" )), FNK::Lit(lit) => lit, }; use SubstAs as SA; let default_buf; let as_ = match as_ { Some(as_) => as_, None => { default_buf = O::default_subst_meta_as(tspan)?; &default_buf } }; match as_ { as_ @ SA::expr(.., np, supported) => { let expr = metavalue_lit_as(lit, tspan, as_)?; supported.infer_type(&expr); out.append_tokens(np, Grouping::Parens.surround(expr))?; } as_ @ SA::ident(..) => { let ident: IdentAny = metavalue_lit_as(lit, tspan, as_)?; out.append_identfrag_toks(&*ident)?; } SA::items(_, np, supported) => { let items = metavalue_lit_as(lit, tspan, &"items")?; supported.infer_type(&items); out.append_tokens(np, items)?; } as_ @ SA::path(..) => out.append_syn_type( tspan, syn::Type::Path(metavalue_lit_as(lit, tspan, as_)?), Grouping::Invisible, ), SA::str(..) => { let s = metavalue_litstr( lit, tspan, format_args!("expected string literal, for meta value",), )?; out.append_syn_litstr(s); } as_ @ SA::ty(..) => out.append_syn_type( tspan, metavalue_lit_as(lit, tspan, as_)?, Grouping::Invisible, ), SA::token_stream(_, np) => { let tokens: TokenStream = metavalue_lit_as(lit, tspan, &"tokens")?; out.append_tokens(np, tokens)?; } } Ok(()) } } //==================== implementations - usage checking ==================== impl Parse for CheckUsed { fn parse(input: ParseStream) -> syn::Result { let la = input.lookahead1(); Ok(if la.peek(Token![*]) { let _star: Token![*] = input.parse()?; mCU::Unchecked } else if la.peek(token::Bracket) { let group: proc_macro2::Group = input.parse()?; let content = group.stream(); mCU::Check(UsedGroup { content }) } else { return Err(la.error()); }) } } impl Recognised { /// Ensures that `self[k] >= v` pub fn update(&mut self, k: Desig, v: Usage) { let ent = self.map.entry(k).or_insert(v); *ent = cmp::max(*ent, v); } } impl ToTokens for Recognised { fn to_tokens(&self, out: &mut TokenStream) { for (desig, allow) in &self.map { match allow { U::BoolOnly => { out.extend(quote! { ? }); } U::Value => {} } desig.to_tokens(Span::call_site(), out); } } } impl PreprocessedTree { pub fn update_used(&self, ra: Usage) { self.used.set(cmp::max(self.used.get(), Some(ra))); } } //---------- decoding used metas ---------- impl PreprocessedValueList { fn decode_update_used(&self, input: ParseStream) -> syn::Result<()> { use PreprocessedValue as PV; for ptree in &self.content { if input.is_empty() { return Ok(()); } if !input.peek(Token![,]) { let path = input.call(syn::Path::parse_mod_style)?; if path != ptree.path { return Err([ (path.span(), "found"), (ptree.path.span(), "expected"), ].error( "mismatch (desynchronised) incorporating previous expansions' used metas" )); } let used = if input.peek(Token![=]) { let _: Token![=] = input.parse()?; Some(U::Value) } else if input.peek(Token![?]) { let _: Token![?] = input.parse()?; Some(U::BoolOnly) } else { None }; if let Some(used) = used { ptree.update_used(used); } if input.peek(token::Paren) { let inner; let paren = parenthesized!(inner in input); let sub_list = match &ptree.value { PV::Unit | PV::Value { .. } => return Err([ (paren.span.open(), "found"), (ptree.path.span(), "defined"), ].error( "mismatch (tree vs terminal) incorporating previous expansions' used metas" )), PV::List(l) => l, }; sub_list.decode_update_used(&inner)?; } } if input.is_empty() { return Ok(()); } let _: Token![,] = input.parse()?; } Ok(()) } } impl<'c> Context<'c> { pub fn decode_update_metas_used( &self, input: /* group content */ ParseStream, ) -> syn::Result<()> { #[derive(Default)] struct Intended { variant: Option, field: Option>, attr_i: usize, } let mut intended = Intended::default(); let mut visit = |pmetas: &PreprocessedMetas, current_variant: Option<&syn::Ident>, current_field: Option>| { loop { let la = input.lookahead1(); if input.is_empty() { // keep visiting until we exit all the loops return Ok(()); } else if la.peek(Token![::]) { let _: Token![::] = input.parse()?; intended = Intended { variant: Some(input.parse()?), field: None, attr_i: 0, }; } else if la.peek(Token![.]) { let _: Token![.] = input.parse()?; intended.field = Some(match input.parse()? { syn::Member::Named(n) => Either::Left(n), syn::Member::Unnamed(i) => Either::Right(i.index), }); intended.attr_i = 0; } else if { let intended_field_refish = intended .field .as_ref() .map(|some: &Either<_, _>| some.as_ref()); !(current_variant == intended.variant.as_ref() && current_field == intended_field_refish) } { // visit subsequent things, hopefully one will match return Ok(()); } else if la.peek(token::Paren) { // we're in the right place and have found a #[deftly()] let i = intended.attr_i; intended.attr_i += 1; let m = pmetas.get(i).ok_or_else(|| { input.error("more used metas, out of range!") })?; let r; let _ = parenthesized!(r in input); m.decode_update_used(&r)?; } else { return Err(la.error()); } } }; visit(&self.pmetas, None, None)?; WithinVariant::for_each(self, |ctx, wv| { let current_variant = wv.variant.map(|wv| &wv.ident); if !wv.is_struct_toplevel_as_variant() { visit(&wv.pmetas, current_variant, None)?; } WithinField::for_each(ctx, |_ctx, wf| { let current_field = if let Some(ref ident) = wf.field.ident { Either::Left(ident) } else { Either::Right(&wf.index) }; visit(&wf.pfield.pmetas, current_variant, Some(current_field)) }) }) // if we didn't consume all of the input, due to mismatches/ // misordering, then syn will give an error for us } } //---------- encoding used metas --------- impl PreprocessedTree { /// Returns `(....)` fn encode_useds( list: &PreprocessedValueList, ) -> Option { let preamble = syn::parse::Nothing; let sep = Token![,](Span::call_site()); let mut ts = TokenStream::new(); let mut ot = TokenOutputTrimmer::new(&mut ts, &preamble, &sep); for t in &list.content { t.encode_used(&mut ot); ot.push_sep(); } if ts.is_empty() { None } else { Some(proc_macro2::Group::new(Delimiter::Parenthesis, ts)) } } /// Writes `path?=(...)` (or, rather, the parts of it that are needed) fn encode_used(&self, out: &mut TokenOutputTrimmer) { use PreprocessedValue as PV; struct OutputTrimmerWrapper<'or, 'o, 't, 'p> { // None if we have written the path already path: Option<&'p syn::Path>, out: &'or mut TokenOutputTrimmer<'t, 'o>, } let mut out = OutputTrimmerWrapper { path: Some(&self.path), out, }; impl OutputTrimmerWrapper<'_, '_, '_, '_> { fn push_reified(&mut self, t: &dyn ToTokens) { if let Some(path) = self.path.take() { self.out.push_reified(path); } self.out.push_reified(t); } } let tspan = Span::call_site(); if let Some(used) = self.used.get() { match used { U::BoolOnly => out.push_reified(&Token![?](tspan)), U::Value => out.push_reified(&Token![=](tspan)), } } match &self.value { PV::Unit | PV::Value { .. } => {} PV::List(l) => { if let Some(group) = PreprocessedTree::encode_useds(l) { out.push_reified(&group); } } } } } impl<'c> Context<'c> { /// Returns `[::Variant .field () ...]` pub fn encode_metas_used(&self) -> proc_macro2::Group { let parenthesize = |ts| proc_macro2::Group::new(Delimiter::Parenthesis, ts); let an_empty = parenthesize(TokenStream::new()); let mut ts = TokenStream::new(); struct Preamble<'p> { variant: Option<&'p syn::Variant>, field: Option<&'p WithinField<'p>>, } impl ToTokens for Preamble<'_> { fn to_tokens(&self, out: &mut TokenStream) { let span = Span::call_site(); if let Some(v) = self.variant { Token![::](span).to_tokens(out); v.ident.to_tokens(out); } if let Some(f) = self.field { Token![.](span).to_tokens(out); f.fname(span).to_tokens(out); } } } let mut last_variant: *const syn::Variant = ptr::null(); let mut last_field: *const syn::Field = ptr::null(); fn ptr_of_ref<'i, InDi>(r: Option<&'i InDi>) -> *const InDi { r.map(|r| r as _).unwrap_or_else(ptr::null) } let mut encode = |pmetas: &PreprocessedMetas, wv: Option<&WithinVariant>, wf: Option<&WithinField>| { let now_variant: *const syn::Variant = ptr_of_ref(wv.map(|wv| wv.variant).flatten()); let now_field: *const syn::Field = ptr_of_ref(wf.map(|wf| wf.field)); let preamble = Preamble { variant: (!ptr::eq(last_variant, now_variant)).then(|| { last_field = ptr::null(); let v = wv.expect("had WithinVariant, now not"); v.variant.expect("variant was syn::Variant, now not") }), field: (!ptr::eq(last_field, now_field)).then(|| { wf.expect("had WithinField (Field), now not") // }), }; let mut ot = TokenOutputTrimmer::new(&mut ts, &preamble, &an_empty); for m in pmetas { if let Some(group) = PreprocessedTree::encode_useds(m) { ot.push_reified(group); } else { ot.push_sep(); } } if ot.did_preamble().is_some() { last_variant = now_variant; last_field = now_field; } Ok::<_, Void>(()) }; encode(&self.pmetas, None, None).void_unwrap(); WithinVariant::for_each(self, |ctx, wv| { if !wv.is_struct_toplevel_as_variant() { encode(&wv.pmetas, Some(wv), None)?; } WithinField::for_each(ctx, |_ctx, wf| { encode(&wf.pfield.pmetas, Some(wv), Some(wf)) }) }) .void_unwrap(); proc_macro2::Group::new(Delimiter::Bracket, ts) } } //---------- checking used metas ---------- struct UsedChecker<'c, 'e> { current: Vec<&'c syn::Path>, reported: &'e mut HashSet

ToTokensPunctComposable for Option<&&P> where P: ToTokens, P: Default, { fn to_tokens_punct_composable(&self, out: &mut TokenStream) { if let Some(self_) = self { self_.to_tokens(out) } else { P::default().to_tokens(out) } } } //---------- ErrorAccumulator ---------- /// Contains zero or more `syn::Error` /// /// # Panics /// /// Panics if dropped. /// /// You must call one of the consuming methods, eg `finish` #[derive(Debug, Default)] pub struct ErrorAccumulator { bad: Option, defused: bool, } impl ErrorAccumulator { /// Run `f`, accumulate any error, and return an `Ok` pub fn handle_in(&mut self, f: F) -> Option where F: FnOnce() -> syn::Result, { self.handle(f()) } /// Handle a `Result`: accumulate any error, and returni an `Ok` pub fn handle(&mut self, result: syn::Result) -> Option { match result { Ok(y) => Some(y), Err(e) => { self.push(e); None } } } /// Accumulate an error pub fn push(&mut self, err: syn::Error) { if let Some(bad) = &mut self.bad { bad.combine(err) } else { self.bad = Some(err); } } /// If there were any errors, return a single error that combines them #[allow(dead_code)] pub fn finish(self) -> syn::Result<()> { self.finish_with(()) } /// If there were any errors, return `Err`, otherwise `Ok(success)` pub fn finish_with(self, success: T) -> syn::Result { match self.into_inner() { None => Ok(success), Some(bad) => Err(bad), } } /// If there any errors, return a single error that combines them pub fn into_inner(mut self) -> Option { self.defused = true; self.bad.take() } } impl Drop for ErrorAccumulator { fn drop(&mut self) { assert!(panicking() || self.defused); } } //---------- Template and driver export ---------- /// Token `export` (or `pub`), indicating that a macro should be exported /// /// Usually found in `Option`. #[derive(Debug, Clone)] pub struct MacroExport(Span); impl Spanned for MacroExport { fn span(&self) -> Span { self.0 } } impl MacroExport { pub fn parse_option(input: ParseStream) -> syn::Result> { let span = if let Some(vis) = input.parse::>()? { return Err(vis.error( "You must now write `define_derive_deftly! { export Template: ... }`, not `puib Template:`, since derive-deftly version 0.14.0" )); } else if let Some(export) = (|| { use syn::parse::discouraged::Speculative; input.peek(syn::Ident).then(|| ())?; let forked = input.fork(); let ident: syn::Ident = forked.parse().expect("it *was*"); (ident == "export").then(|| ())?; input.advance_to(&forked); Some(ident) })() { Some(export.span()) } else { None }; Ok(span.map(MacroExport)) } } //---------- Grouping ---------- /// Whether an expansion should be surrounded by a `None`-delimited `Group` /// /// `Ord` is valid for composition with `cmp::max` #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] pub enum Grouping { Ungrouped, Invisible, Parens, } impl Grouping { pub fn surround(&self, ts: impl ToTokens) -> TokenStream { let ts = ts.to_token_stream(); match self { Grouping::Ungrouped => ts, Grouping::Invisible => { proc_macro2::Group::new(Delimiter::None, ts).to_token_stream() } Grouping::Parens => { proc_macro2::Group::new(Delimiter::Parenthesis, ts) .to_token_stream() } } } } //---------- IdentAny ---------- /// Like `syn::Ident` but parses using `parse_any`, accepting keywords /// /// Used for derive-deftly's own keywords, which can be Rust keywords, /// or identifiers. /// /// Needs care when used with user data, since it might be a keyword, /// in which case it's not really an *identifier*. pub struct IdentAny(pub syn::Ident); impl Parse for IdentAny { fn parse(input: ParseStream) -> syn::Result { Ok(IdentAny(Ident::parse_any(input)?)) } } impl Deref for IdentAny { type Target = syn::Ident; fn deref(&self) -> &syn::Ident { &self.0 } } impl ToTokens for IdentAny { fn to_tokens(&self, out: &mut TokenStream) { self.0.to_tokens(out) } } impl + ?Sized> PartialEq for IdentAny { fn eq(&self, rhs: &T) -> bool { self.0.eq(rhs) } } //---------- OutputTrimmer ---------- /// For making an output TokenStream, but eliding an unnecessary tail /// /// This construction will write, to an output [`TokenStream`], /// /// * `preamble` /// * zero or more optional `impl ToTokens`, called "reified" /// * interleaved with zero or more optional separators `sep` /// /// But it will avoid writing trailing unnecessary content: /// that is, trailing calls to `push_sep` are ignored, /// and if `push_reified` is never called, /// the preamble is also omitted. pub struct TokenOutputTrimmer<'t, 'o> { preamble: Option<&'t dyn ToTokens>, sep: &'t dyn ToTokens, sep_count: usize, out: &'o mut TokenStream, } impl<'t, 'o> TokenOutputTrimmer<'t, 'o> { pub fn new( out: &'o mut TokenStream, preamble: &'t dyn ToTokens, sep: &'t dyn ToTokens, ) -> Self { TokenOutputTrimmer { preamble: Some(preamble), sep, sep_count: 0, out, } } pub fn push_sep(&mut self) { self.sep_count += 1; } fn reify(&mut self) { if let Some(preamble) = self.preamble.take() { preamble.to_tokens(&mut self.out); } for _ in 0..mem::take(&mut self.sep_count) { self.sep.to_tokens(&mut self.out); } } pub fn push_reified(&mut self, t: impl ToTokens) { self.reify(); t.to_tokens(&mut self.out); } /// Did we output the preamble at all? pub fn did_preamble(self) -> Option<()> { if self.preamble.is_some() { None } else { Some(()) } } } //---------- TemplateName ---------- #[derive(Debug, Clone)] pub struct TemplateName(syn::Ident); impl TemplateName { pub fn macro_name(&self) -> syn::Ident { format_ident!("derive_deftly_template_{}", &self.0) } } impl Parse for TemplateName { fn parse(input: ParseStream) -> syn::Result { let ident: syn::Ident = input.parse()?; ident.try_into() } } impl TryFrom for TemplateName { type Error = syn::Error; fn try_from(ident: syn::Ident) -> syn::Result { let s = ident.to_string(); match s.chars().find(|&c| c != '_') { None => { Err("template name cannot consist entirely of underscores") } Some(c) => { if c.is_lowercase() { Err( "template name may not start with a lowercase letter (after any underscores)" ) } else { Ok(()) } } } .map_err(|emsg| ident.error(emsg))?; Ok(TemplateName(ident)) } } impl ToTokens for TemplateName { fn to_tokens(&self, out: &mut TokenStream) { self.0.to_tokens(out) } } impl Display for TemplateName { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { Display::fmt(&self.0, f) } } impl quote::IdentFragment for TemplateName { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { quote::IdentFragment::fmt(&self.0, f) } fn span(&self) -> Option { quote::IdentFragment::span(&self.0) } } //---------- engine_macro_name ---------- /// Return a full path to the location of `derive_deftly_engine`. /// /// (This may not work properly if the user /// imports the crate under a different name. /// This is a problem with the way cargo and rustc /// handle imports and proc-macro crates, /// which I think we can't properly solve here.) pub fn engine_macro_name() -> Result { let name = crate_name("derive-deftly-macros") .or_else(|_| crate_name("derive-deftly")); // See `tests/pub-export/pub-b/pub-b.rs`. (The bizarre version // has a different crate name, which we must handle heree.) #[cfg(feature = "bizarre")] let name = name.or_else(|_| crate_name("bizarre-derive-deftly")); match name { Ok(FoundCrate::Itself) => Ok(quote!( crate::derive_deftly_engine )), Ok(FoundCrate::Name(name)) => { let ident = Ident::new(&name, Span::call_site()); Ok(quote!( ::#ident::derive_deftly_engine )) } Err(e) => Err(Span::call_site().error( format_args!("Expected derive-deftly or derive-deftly-macro to be present in Cargo.toml: {}", e) )), } } //---------- general keyword enum parsing ---------- /// General-purpose keyword parser /// /// ```ignore /// keyword_general!{ /// KW_VAR FROM_ENUM ENUM; /// KEYWORD [ {BINDINGS} ] [ CONSTRUCTOR-ARGS ] } /// ``` /// Expands to: /// ```ignore /// if KW_VAR = ... { /// BINDINGS /// return FROM_ENUM(ENUM::CONSTRUCTOR CONSTRUCTOR-ARGS) /// } /// ``` /// /// `KEYWORD` can be `"KEYWORD_STRING": CONSTRUCTOR` /// /// `CONSTRUCTOR-ARGS`, if present, should be in the `( )` or `{ }` /// as required by the variant's CONSTRUCTOR. macro_rules! keyword_general { { $kw_var:ident $from_enum:ident $Enum:ident; $kw:ident $( $rest:tt )* } => { keyword_general!{ $kw_var $from_enum $Enum; @ 1 stringify!($kw), $kw, $($rest)* } }; { $kw_var:ident $from_enum:ident $Enum:ident; $kw:literal: $constr:ident $( $rest:tt )* } => { keyword_general!{ $kw_var $from_enum $Enum; @ 1 $kw, $constr, $($rest)* } }; { $kw_var:ident $from_enum:ident $Enum:ident; @ 1 $kw:expr, $constr:ident, $( $ca:tt )? } => { keyword_general!{ $kw_var $from_enum $Enum; @ 2 $kw, $constr, { } $( $ca )? } }; { $kw_var:ident $from_enum:ident $Enum:ident; @ 1 $kw:expr, $constr:ident, { $( $bindings:tt )* } $ca:tt } => { keyword_general!{ $kw_var $from_enum $Enum; @ 2 $kw, $constr, { $( $bindings )* } $ca } }; { $kw_var:ident $from_enum:ident $Enum:ident; @ 2 $kw:expr, $constr:ident, { $( $bindings:tt )* } $( $constr_args:tt )? } => { let _: &IdentAny = &$kw_var; if $kw_var == $kw { $( $bindings )* return $from_enum($Enum::$constr $( $constr_args )*); } }; { $($x:tt)* } => { compile_error!(stringify!($($x)*)) }; } work/maint/0000775000000000000000000000000014763657303010056 5ustar work/maint/build-docs-local0000775000000000000000000001770214763657303013130 0ustar #!/usr/bin/env python3 # # nailing-cargo --- maint/build-docs-local # nailing-cargo --- maint/build-docs-local --dev import argparse import io import os import re import subprocess import sys import toml.decoder import typing from typing import Dict, List, Tuple, Callable, Any, TYPE_CHECKING #---------- type handling ---------- if TYPE_CHECKING: CompletedProcess = subprocess.CompletedProcess[Any] else: CompletedProcess = subprocess.CompletedProcess #---------- settings that could perhaps become parameters ---------- rustdoc_packages = [ 'derive-deftly', 'derive-deftly-macros', 'derive-deftly-tests', ] rustdoc_opts = ['--all-features'] docsrs_url_pat = 'https://docs.rs/%s/%s/%s' mdbook_dir = 'book' mdbook_url = 'https://diziet.pages.torproject.net/rust-derive-deftly/latest/guide' mdbook_input_dir = mdbook_dir + '/book/html' mdbook_slug = 'guide' target_dir = 'target' out_unified = 'doc-unified' # lives in target/ toml_data: Dict[str, Any] #---------- preparation and utilities ---------- rustdoc_rustc_packages = list([ p.replace('-','_') for p in rustdoc_packages ]) def raise_fn(e: Exception) -> None: raise(e) def mdbook_leaf() -> str: return 'mdbook-%s' % mdbook_slug if sys.version_info >= (3, 9): Pattern_str = re.Pattern[str] else: Pattern_str = typing.Any #---------- link massager ---------- class Massager: def __init__(self) -> None: ''' Create a new massager, suitable for handling several files. Initially, it doesn't actually change any of the files' contents. ''' self._replacements: List[Tuple[Pattern_str, str]] = [] def add(self, url: str, path: str) -> None: ''' Record that `url` should be replaced with links to subdir `path` `path` is a directory path relative to `out_unified` ''' url_re = re.escape(url) i_re = r'(?<=")' + url_re + r'/*(?=["#])' self._replacements.append((re.compile(i_re), path + '/index.html')) m_re = r'(?<=")' + url_re + r'/+' self._replacements.append((re.compile(m_re), path + '/')) def process_tree(self, in_tree: str, out_leaf: str) -> None: ''' Process all files under `in_tree`, writing to `out_leaf` The previously `add`ed substitutions will be made (using appropriately calculated relative paths in each case). `in_tree` is relative to `.`, where `target` also lives. `out_leaf` is a leaf directory within `out_unified` ''' #print('in_tree=%s out_leaf=%s' % (in_tree, out_leaf), file=sys.stderr) for dirpath, dirnames, filenames in os.walk(in_tree, onerror=raise_fn): if not dirpath.startswith(in_tree): raise subdir_s = dirpath[len(in_tree):].lstrip('/') if subdir_s == "": subdir = [] else: subdir = re.split(r'/+', subdir_s) depth = len(subdir) #print('depth==%d subdir=%s' % (depth, repr(subdir)), file=sys.stderr) out_dir = '/'.join([target_dir, out_unified, out_leaf] + subdir) up = '../' * (depth + 1) os.makedirs(out_dir, exist_ok=True) repls = [ (repl_re, up + repl_path) for repl_re, repl_path in self._replacements ] for f in filenames: out_file = '%s/%s' % (out_dir, f) in_file = '%s/%s' % (dirpath, f) try: self._process_file(repls, f, in_file, out_file) except: print('Error generating %s' % out_file, file=sys.stderr) raise def _process_file(self, repls: List[Tuple[Pattern_str, str]], f: str, in_file: str, out_file: str) -> None: out_tmp = out_file + '.tmp' if f.endswith('.html'): data = open(in_file, 'r', encoding='utf-8').read() for repl_re, repl_fn in repls: data = repl_re.sub(repl_fn, data) out = open(out_tmp, 'w', encoding='utf-8') out.write(data) out.flush() else: try: os.remove(out_tmp) except FileNotFoundError: pass os.link(in_file, out_tmp) os.rename(out_tmp, out_file) #---------- pieces of the main program, and main itself ---------- def build_rustdocs(cli_rustdoc_arg: List[str]) -> None: shfrag = ''' : ${CARGO:=cargo} set -x $CARGO $NAILINGCARGO_CARGO_OPTIONS doc "$@" ''' cmd = ['sh','-ec', shfrag, 'x'] + rustdoc_opts + cli_rustdoc_arg for p in rustdoc_packages: cmd += ['-p', p] subprocess.run(cmd, check=True) def build_massager() -> Massager: massager = Massager(); massager.add(mdbook_url, mdbook_leaf()) # Quadratic in rustdoc_packages, but it will do for p in rustdoc_packages: for p_rustc in rustdoc_rustc_packages: massager.add(docsrs_url_pat % (p, 'latest', p_rustc), p_rustc) main_p_rustdoc = rustdoc_packages[0] main_p_rustc = rustdoc_rustc_packages[0] main_p_versioned_url = docsrs_url_pat % ( main_p_rustdoc, toml_data['package']['version'], main_p_rustc ) massager.add(main_p_versioned_url, main_p_rustc) return massager def massage_rustdocs() -> None: massager = build_massager() for d in rustdoc_rustc_packages: massager.process_tree( '%s/doc/%s' % (target_dir, d), d, ) # ^ that created the output toplevel directory as a side-effect for d in os.listdir('%s/doc' % target_dir): if d in rustdoc_rustc_packages: continue d_o = '/'.join([target_dir, out_unified, d]) # shutil.rmtree descends into this, if it's already a link subprocess.run(['rm','-rf','--',d_o], check=True) os.symlink('../doc/' + d, d_o) def build_mdbook(cli_mdbook_arg: List[str]) -> None: cmd = ['maint/build-mdbook'] + cli_mdbook_arg subprocess.run(['sh', '-xec', '"$@"', 'x'] + cmd, check=True) def massage_mdbook() -> None: massager = build_massager() massager.process_tree( mdbook_input_dir, mdbook_leaf(), ) def check_links_1() -> CompletedProcess: return subprocess.run(['maint/check-doc-links']) def check_links_2(done: CompletedProcess) -> None: if done.returncode == 1: print('** link check failed - broken doc hyperlinks **\n', file=sys.stderr) sys.exit(1) else: done.check_returncode() def print_unified_urls() -> None: print(''' unified docs, links adjusted for local reading, available in:''') cwd = os.getcwd() for subdir in [rustdoc_rustc_packages[0], mdbook_leaf()]: d = '/'.join([cwd, target_dir, out_unified, subdir, "index.html"]) print(' file://%s' % d) print('') def main() -> None: parser = argparse.ArgumentParser() parser.add_argument('--dev', '-D', dest='rustdoc_arg', action='append_const', const='--document-private-items', default=[], help='Document innards (pass --document-private-items to rustdoc)') parser.add_argument('--rustdoc-arg', '-R', action='append', default=[], help='Pass an option through to cargo doc (rustdoc)') parser.add_argument('--mdbook-arg', '-M', action='append', default=[], help='Pass an option through to mdbook') parser.add_argument('--no-linkcheck', dest='linkcheck', action='store_false', default=True, help='Do not check internal hyperlinks') args = parser.parse_args() build_rustdocs(args.rustdoc_arg) build_mdbook(args.mdbook_arg) global toml_data toml_data = toml.decoder.load('Cargo.toml') massage_rustdocs() massage_mdbook() if args.linkcheck: links_checked = check_links_1() else: links_checked = None print_unified_urls() if not(links_checked is None): check_links_2(links_checked) main() work/maint/build-mdbook0000775000000000000000000000170514763657303012357 0ustar #!/bin/bash # # Build just our mdbook output # # This is an internal script, normally run by build-docs-local. # # nailing-cargo --- maint/build-mdbook set -e set -o pipefail function usage() { cat < X X X set -e set -o pipefail version_re=$(perl ; $d = from_toml($d) or die $!; my $version = $d->{package}{version} // die $!; $version =~ s/\W/\\$&/g; printf "%s\n", $version or die $!; '); rcs=' ' check1 () { set +e "$@" rcs+="$? " set -e } check1 git --no-pager grep -P -i '\bfixme\b' check1 git --no-pager grep -P '(?&2 'Found FIXMEs/XXXs - RC TODS - in the codebase!' exit 1 ;; *) echo >&2 'git grep failed!' exit 16 ;; esac work/maint/check-doc-links0000775000000000000000000000162214763657303012743 0ustar #!/bin/bash # # Check rustdoc HTML links and anchors # # This is an internal script, usually run by maint/build-docs-local set -e set -o pipefail # nailing-cargo --- maint/check-doc-links if [ "$#" != 0 ]; then echo >&2 "$0: bad usage"; exit 12; fi chk_dir=target/doc-unified.link-check rm -rf target/doc-unified.link-check cp -al target/doc-unified $chk_dir # Fix up https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=425632 # And also href="#" links that rustdoc likes to put in for some reason find $chk_dir -name \*.html -print0 | xargs -0r -- perl -i~ -pe ' s{\bid=("[^"]+")[^<>]*\>}{$&}g; s{\bhref="#"}{}g; ' linklint -out linklint.errors -error -root $chk_dir \ /derive_deftly/@ /mdbook-guide/@ cat linklint.errors set +e grep ERROR linklint.errors rc=$? set -e case $rc in 0) echo >&2 '** found linkcheck errors **'; exit 1;; 1) ;; *) echo >&2 "linkcheck failed $rc"; exit 1;; esac work/maint/check-keywords-documented0000775000000000000000000002435714763657303015066 0ustar #!/usr/bin/perl -w # # This script checks that everything that exists is documented, # and vice versa. # # It does some pretty grotty ad-hoc parsing of both Rust sources, # and the markdown in the reference manual. # # At the bottom of the file, in __DATA__, is a table of expected # exceptions. # # # With --json it outputs information about what is found where, # as a JSON document. (The schema is not currently documented.) # (In this mode it doesn't perform most of the checks.) # # Implementation note: # # (I experimented with a more formal approach, including having the # `bizarre` version of d-d-macros collect the fixed strings that a keyword # was compared with during parsing. It was difficult to properly # categorise the keyword and the mechanism was confusing and invasive.) use strict; use JSON qw(to_json); our ($whole, $file); our $print_progress = 1; sub progress (@) { if ($print_progress) { print @_; } } sub open_file ($) { ($file) = @_; open F, '<', $file or die "$file: $!"; progress " reading $file\n"; } sub end_file () { F->error and die "$file: $!"; } sub read_file ($) { open_file $_[0]; local $/ = undef; $whole = // die "$file: $!"; end_file(); } our %grokked; our %ref_lines; sub grokked ($$@) { my ($info, $note, @out) = @_; progress " grokked $info [$note]\n @out\n\n"; foreach my $kw (@out) { $grokked{$kw} .= $note; } } sub grok_match ($$$$) { my ($file, $tag, $note, $unreach) = @_; read_file($file); my @out; my $any; while ($whole =~ m{ ( \ + ) // .* \#\# \ maint/check-keywords-documented \ $tag \ \#\# .* \n ( (?: \1 .* \n ) + ) }gmx) { $any++; my $arm = $2; $arm =~ s{ \=\> .* $unreach .* \n }{}x or die "$tag arm not => $unreach"; $arm !~ m{\=\>} or die "----- ARM -----\n$arm\n----- ARM -----\n". "multiple arms pasted together, parsing failed?"; push @out, $arm =~ m{ SD :: (\w+) }gmx; } die "// ## maint/check-keywords-documented $tag ## not found" unless $any; grokked $tag, $note, map { lc } @out; } sub grok_not_in_bool () { grok_match('macros/boolean.rs', 'NotInBool', 'e', qr{void::unreachable}); } sub grok_bool_only () { grok_match('macros/expand.rs', 'BoolOnly', 'b', qr{append_bool_only}); } sub grok_enum () { read_file('macros/syntax.rs'); $whole =~ m{ ^ pub \ enum \ SubstDetails\b .* \{ \n ( (?: \ {4} .* \n | \n )* ) ^ \} $ }mx or die "pub enum SubstDetails not found"; my $enum_body = $1; my @out = $enum_body =~ m{^ \ {4} (\w+) \b}gmx; grokked 'enum variants', 'v', map { lc } @out; } sub grok_cases () { open_file('macros/paste.rs'); my (@outk, @outh); while () { next unless m/^define_cases\! *\{$/..m/^\}$/; push @outk, m{\"(\w+)\"}g; push @outh, m{\bAs(\w+)\b}g; } grokked 'define_cases (kwd)', 'c', @outk; grokked 'define_cases (heck)', 'h', @outh; end_file(); } sub grok_dbg_allkw () { open_file('macros/dbg_allkw.rs'); my (@out_kw, @out_bool); while () { my $dollar_re; my $out; if (m/^\s* expand\!/x) { $dollar_re = qr{\$}; $out = \@out_kw; } elsif (m/^\s* (bool)\!/x) { $dollar_re = qr{}; $out = \@out_bool; } else { next; } my $name_re = qr{\#?\w+}; die "couldn't grok dbg_allkw macro invocation ($dollar_re) $_ ?" unless m/^\ * \w+\! \ * \{ \ * c, \ * $dollar_re ($name_re) \ * \}$/x || m/^\ * \w+\! \ * \{ \ * c, \ * $dollar_re \{ ($name_re) [^{}]* \} \ * \}$/x; next if $1 =~ m{^\#}; push @$out, $1; } grokked 'dbg_all_keywords (expand!)', 'd', @out_kw; grokked 'dbg_all_keywords (bool!)', 'D', @out_bool; end_file(); } sub grok_document () { open_file 'doc/reference.md'; my $in; my %out; my @case_cols; my $lno = 0; my $last_heading_lno; while () { $lno++; if (m{\#\# maint/check-keywords-documented (\w+) \#\#}) { $in = $1; next; } if (m{^\#\# }) { $in = undef; $last_heading_lno = $lno; next; } next unless defined $in; my $out = \@{ $out{$in} }; my $found = sub { my ($div_prefix_char, $lno, @l) = @_; @l = grep { $_ ne 'CASE_CHANGE' } @l; push @$out, @l; foreach my $kw (@l) { push @{ $ref_lines{"$div_prefix_char:$kw"} }, $lno; } }; if ($in eq 'cases') { next unless m{^\|}; my @cols = split m{\|}; if (!@case_cols) { @case_cols = @cols; next; } next if m{^\|-----}; foreach my $i (0..$#cols) { my @l = $cols[$i] =~ m{\`(\w+)\`}g; if ($case_cols[$i] =~ m{\`heck\`}) { push @{ $out{heck} }, @l; } elsif ($case_cols[$i] =~ m{\`CASE_CHANGE\`}) { $found->('x', $last_heading_lno, @l); } } } elsif ($in eq 'conditions') { next unless m{^\#\#\# }; $found->('c', $lno, m{\`(\w+)[^\`]*\`}g); } elsif ($in eq 'expansions') { next unless m{^\#\#\# }; # remove inner { }, repeatedly while (s{ ( \{ [^{}]* ) \{ [^{}]* \} ( .* \} ) }{$1 $2}x) { } $found->('x', $lno, m{\`\$(\w+)\`}g); $found->('x', $lno, m{\`\$\{(\w+)[^\}]+\}\`}g); } else { die "bad maint/check-keywords-documented $in !"; } } end_file(); my $grokked = sub { my ($note, $in) = @_; grokked $in, $note, @{ $out{$in} }; }; $grokked->('E', 'expansions'); $grokked->('C', 'cases'); $grokked->('H', 'heck'); $grokked->('B', 'conditions'); } our @discrepant; our %note_descriptions; sub correspondences () { my %expected; while () { s{^\s+}{}; s{\s+$}{}; next if m{^\#}; next unless m{\S}; if (m{^\: \s+ (\S) \s+ (\S.*)}x) { $note_descriptions{$1} = $2; next; } my ($exp, @l) = split /\s+/; die unless @l; foreach my $kw (@l) { die "duplicate exception for $kw " if $expected{$kw}; $expected{$kw} = $exp; } } foreach my $kw (sort keys %grokked) { $_ = $grokked{$kw}; # Expected combinations, which are not troubling: next if m{^vbDB$}; # ordinary bool-only keyword next if m{^vedE$}; # ordinary expansion-only keyword next if m{^vdDEB$}; # ordinary keyword, both bool and expansion next if m{^hH$}; # heck keyword (only) next if m{^cCC?$}; # case changing (possibly also example) next if m{^chCH$}; # case changing and heck keyword # This one is unusual, is it in the expected exception table? my $exp = $expected{$kw}; if ($exp) { delete $expected{$kw}; next if $_ eq $exp; } push @discrepant, { Kw => $kw, Got => $_, Exp => $exp }; } foreach my $kw (keys %expected) { push @discrepant, { Kw => $kw, Exp => $expected{$kw} }; } } sub print_divider () { print "------------------------------------------------------------\n"; } sub print_discrepancies() { print "discrepancies\n"; foreach my $disc (@discrepant) { printf " keyword %-20s found %s", $disc->{Kw}, $disc->{Got}; if ($disc->{Exp}) { printf " expected %s", $disc->{Exp}; } print "\n"; } print_divider(); flush STDOUT; print STDERR <{Kw}; my $print_notes = sub { my ($heading, $notes, $if_none) = @_; if (!$notes) { printf STDERR "%s\n", $if_none; return; } printf STDERR "%s\n", $heading; if ($notes) { foreach my $note (split //, $notes) { printf STDERR " %s\n", $note_descriptions{$note} // "UNDESCRIBED `$note`"; } foreach my $note (keys %note_descriptions) { next if $notes =~ m/$note/; # elide "not:" for case changing stuff if there's no # hint that this is going to be relevant next if $note =~ m/[hc]/i and $notes !~ m/[hc]/i; printf STDERR " not: %s\n", $note_descriptions{$note}; } } else { printf STDERR " None.\n"; } }; $print_notes->( "Information and properties found in docs and source code:", $disc->{Got}, 'Keyword does not exist.' ); $print_notes->( "Entry in exceptions table -- expected to find:", $disc->{Exp}, "No entry in exceptions table.\n", ); printf STDERR "To suppress, add exceptions with note chars `%s`.\n", $disc->{Got} if $disc->{Got}; printf STDERR "|\n"; } printf STDERR < \%grokked, ref_lines => \%ref_lines, }) or die $!; } our $mode = 'check'; while (@ARGV) { $_ = shift @ARGV; if (m/^--json$/) { $mode = 'json'; } else { die "unsupported option $_"; } } my $mode_fn = ${*::}{"mode_$mode"} or die; $mode_fn->(); __DATA__ # Expected exceptions table # Each line is [ ...] # (The lines starting `:` are used for text in the discrepancy reports.) # # Note characters have the following meanings: # Found in source code: : v enum SubstDetails variant, in syntax.rs : e expansion-only variant, ie NotInBool, according to boolean.rs : c case change keyword, according to from define_cases! in paste.rs : h heck name for a case change, according to define_cases! in paste.rs : b BoolOnly variant, according to expand.rs # Found in reference documentation: : E Documented as expansion keyword : C Documented as case change keyword : H Documented as heck name of a case change : B Documented as condition (BoolOnly keyword) # Found in dbg_all_keywords: : d $dbg_all_keywords dumps the expansion : D $dbg_all_keywords dumps as a boolean # we use placeholders in the docs, and handle case changing # with its own combined SubstDetails variant ve changecase # These are combined SD variants dDEB fvis fdefvis tvis v vis dDEB fmeta vmeta tmeta v xmeta # Doc has both ${for fields ...} and ${for variants ...}, so E is repeated veEE for # User-defined keywords v userdefined work/maint/check-test-deps0000775000000000000000000000232014763657303012764 0ustar #!/usr/bin/perl -w # # Checks that the dependencies used by the direct tests in # derive-deftly-tests, are the same ones as specified in the # derive-deftly-macros Cargo.toml. use strict; use IO::Handle; use TOML qw(from_toml); sub read_file ($) { my ($file) = @_; local $/ = undef; open C, $file or die "$file $!"; my $toml = // die "$file $!"; C->error and die "$file $!"; $toml = from_toml($toml) || die "$file ?"; return $toml; } my $macros = read_file 'macros/Cargo.toml'; my $tests = read_file 'tests/Cargo.toml'; sub normalise ($) { my ($info) = @_; if (!ref $info) { $info = { version => $info }; } ($info, $info->{version} // '') } our $bad; sub bad ($) { print STDERR "mismatch: $_[0]\n"; $bad++; } foreach my $p (sort keys %{ $macros->{dependencies} }) { my ($mi, $mv) = normalise($macros->{dependencies}{$p}); my $ti = $tests->{dependencies}{$p}; if (!defined $ti) { bad "missing dependency $p"; next; } my $tv; ($ti, $tv) = normalise($ti); if ($mv ne $tv) { bad "dependency $p macros have $mv tests have $tv"; } } if ($bad) { die "Some dependencies in macros/Cargo.toml aren't matched in tests/\n"; } work/maint/feature-matrix-test0000775000000000000000000000467314763657303013730 0ustar #!/usr/bin/env python3 # # Tests the toplevel crate with *every* combination of cargo features. # (But, not any sets that don't include any `minimal-*` feature.) # # This script, obviously, runs cargo a number of times which is # *exponential* in the number of cargo features. # # But, most of those builds don't do very much, because cargo caches # builds of a particular crate with particular features separately, # and can retain multiple versions. # # And the set of combinations that cargo needs to build for is also # reduced a bit because it's the *effective* feature set that counts. # # In practice a from scratch run of this script currently takes 65s # on my laptop. That seems fine. But this is why we're just running # `cargo check` and not actually running any of the tests. import argparse import subprocess import toml.decoder from typing import Iterable, Tuple, TypeVar from itertools import chain, combinations T = TypeVar('T') # Copied from https://docs.python.org/3/library/itertools.html # retrieved 2024-06-21. Licence there is as follows: # This page is licensed under # the Python Software Foundation License Version 2. # Examples, recipes, and other code in the documentation are # additionally licensed under the Zero Clause BSD License. def powerset(iterable: Iterable[T]) -> Iterable[Iterable[T]]: "powerset([1,2,3]) → () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)" s = list(iterable) return chain.from_iterable(combinations(s, r) for r in range(len(s)+1)) def main() -> None: parser = argparse.ArgumentParser() args = parser.parse_args() t = toml.decoder.load('Cargo.toml') features = list(t['features']) print(repr(features)) for f in features: print('found feature %s' % f) combs = [] for feats in powerset(features): print("considering: %s" % ' '.join(feats)) if not(any([s.startswith('minimal-') for s in feats])): print('no minimal-* feature enabled') continue combs.append(feats) for i, feats in enumerate(combs): print('==================== %d/%d ====================' % (i, len(combs)), flush=True) scriptlet = ''' ${CARGO-cargo} check --no-default-features $1 ''' cmd = ['sh','-xec', scriptlet, 'x', '--features=' + ','.join(feats)] subprocess.run(cmd, check=True) print('==================== %d complete, ok ====================' % len(combs)) main() work/maint/for-every-commit0000775000000000000000000000746414763657303013223 0ustar #!/bin/bash # # We fail CI if the word "F I X M E" is detedcted, as a whole word, # in any case. The word is not matched if there are word-characters # abutted to it. set -e set -o pipefail BASE_BRANCHES='main' repo=$( perl -MTOML -we ' use strict; undef $/; my $toml = from_toml(); print $toml->{package}{repository} // die; ' /dev/null 2>&1 ||: git remote rename upstream upstream.tmp >/dev/null 2>&1 ||: git remote add upstream "$repo" ORIGIN=upstream ;; '') # Not running in CI, use a guess at the baseline: # $BASE_BRANCHES at whatever `origin` points to. ORIGIN=origin ;; *) # We are apparently running in a different instance to upstream. # It's not clear what this testing would mean. # Should it use our repo URL? That would be quite exciting. echo >&2 "CI_PROJECT_URL $CI_PROJECT_URL not recognised!" exit 4 ;; esac echo "Baseline repo remote $ORIGIN, at:" git remote get-url "$ORIGIN" tlref () { echo "refs/remotes/$ORIGIN/$tbranch" } x () { echo "+ $*" "$@" } git_checkout_maybe_clean () { x=$1; shift if $x git checkout -q "$1" then : else echo "Checkout failed, trying with git clean and git reset" x git clean -xdff x git reset --hard x git checkout -q "$1" fi } old_branch=$(git symbolic-ref -q HEAD || test $? = 1) restore_old_branch () { case "$old_branch" in refs/heads/*) git_checkout_maybe_clean '' "${old_branch#refs/heads/}" || echo '*** Failed to return to original branch ***' ;; *) echo "*** Was not originally on a branch, HEAD changed! ***" ;; esac } trap 'restore_old_branch' 0 refspecs=() for tbranch in $BASE_BRANCHES; do refspecs+=(+"refs/heads/$tbranch:$(tlref)") done # If you try to unshallow a repo that's already complete, you get # an error! Hence this. Not all versions of git-rev-parse # understand this option; we err on the side of trying the unshallow. if [ "x$(git rev-parse --is-shallow-repository)" != xfalse ]; then # TODO really we want something like the converse of # git fetch --shallow-exclude # but it doesn't seem to exist. git fetch --unshallow "$ORIGIN" "${refspecs[@]}" fi for tbranch in $BASE_BRANCHES; do trevlist="$(tlref)..HEAD" tcount=$(git rev-list --count "$trevlist") printf "HEAD is %3d commits ahead of %s\n" "$tcount" "$tbranch" if [ "$count" ] && [ $count -le $tcount ]; then continue; fi count=$tcount branch=$tbranch revlist=$trevlist done echo "Testing every commit not already on $branch" commits=$(git rev-list --reverse "$revlist") i=0 for commit in $commits; do banner='##############################' printf "|\n%s %d/%d %s\n|\n" "$banner" "$i" "$count" "$banner" git log -n1 "$commit" | sed 's/^/| /' echo '|' git_checkout_maybe_clean x "$commit" if x "$@"; then :; else rc="$?" printf " | ========================= Failed after $i/$count ========================= | Earliest broken commit is " git --no-pager log -n1 --pretty=oneline "$commit" exit "$rc" fi i=$(( $i + 1 )) done echo "| $banner ok $banner |" rc=0 work/maint/fudge-for-docs-test-build0000775000000000000000000000050714763657303014664 0ustar #!/bin/sh # # # README.md has unbalanced HTML tags, because of our desire to # add an "internal" top-level heading. # # We have an `#[allow]` for this, but its scope is too wide. # So in CI delete (1) the unmatched div (2) the unwanted allow. set -e perl -i~ -pe 's{.* \@derive-deftly invalid-html-tags.*}{}' \ src/lib.rs work/maint/navbar0000664000000000000000000000106214763657303011251 0ustar divider = | $ text =

work/maint/rustfmt0000775000000000000000000000144614763657303011515 0ustar #!/bin/bash # # Run rustfmt on all the files. This script is needed because cargo # fmt doesn't do the ones in tests/* (that are tested via trybuild and # macrotest), because those aren't visible to cargo in the usual way. # # Any arguments are passed through to *both* "cargo fmt" and "rustfmt" set -euo pipefail # CARGO='nailing-cargo -E' maint/rustfmt case "${CARGO-}" in *nailing-cargo*) NAILING_CARGO=${CARGO} ;; *) NAILING_CARGO= ;; esac ${CARGO-cargo} fmt "$@" # *.expanded.rs is managed by macrotest and mustn't be reformatted # (the grep is because git-ls-files -x doesn't do what you'd think it would - # it only applies if the file is untracked.) git ls-files tests/{ui,expand}/'*.rs' | grep -v '\.expanded\.rs$' | xargs \ ${NAILING_CARGO} ${NAILING_CARGO:+---} \ rustfmt "$@" work/maint/update-bizarre0000775000000000000000000000742114763657303012726 0ustar #!/usr/bin/perl -w # # usage: # maint/update-bizarre [--check] # # Updates the Cargo.toml files in tests/pub-export/bizarre-* # from the main ones in /Cargo.toml and /macros/Cargo.toml. # # Also, updates tests/compat/old-b/old-b.rs # from tests/pub-export/pub-b.rs # our $explanation_xref = <<'END'; # See tests/pub-export/pub-b/pub-b.rs for the general explanation # of the bizarre-* crates. END # # We do it like this mostly because build.rs cannot generate Cargo.toml's. # So we would need to have a completely autogenerated crate and re-invoke # cargo build / cargo test from the test case. # # That is best avoided where possible, because it results in compile errors # etc. pointing to autogenerated files, and makes it much harder to # see what is going on in the test output. use Carp; use Getopt::Long; our $check; GetOptions( "check" => \$check, ) or die("$0: bad usage\n"); our $bizvsn = '0.0.666'; our $found_differences = 0; our ($input, $output); sub filter_start ($$$) { die if defined $input; $input = shift @_; $output = shift @_; my ($header) = @_; open I, $input or confess "$input: $!"; open O, ">$output.new" or confess "$output: $!"; print O $header or confess $!; } sub filter_finish () { I->error and confess $!; O->error and confess $!; close O or confess $!; if ($check) { my $r = system 'diff', '-u', "$output", "$output.new"; $r == 0 || $r == 256 || confess 'diff failed'; if ($r) { $found_differences ||= 1; } else { unlink "$output.new" or confess $!; } } else { rename "$output.new", "$output" or confess "$input: $!"; } $input = undef; } sub process_cargo_toml ($$$$$) { my ($input, $output, $src_path, $in_features, $before_lib) = @_; (sub { # anonymous subref has its own `...` operator state filter_start $input, $output, <) { next if m{^\s*\#}; s{(\S)[ \t]+}{$1 }g; s{\s+$}{\n}; if (m{^\[package\]}...m{^\[}) { s{^name ?= ?"([^"]+)"$}{name = "bizarre-$1"} and $pkg=$1; s{^version ?=.*}{version = "$bizvsn"}; s{^(?:homepage|repository|readme) ?=.*\n}{}; $_ = <() } sub process_pub_b_old_b () { filter_start 'tests/pub-export/pub-b/pub-b.rs', 'tests/compat/old-b/old-b.rs', <) { s{^\s*//.*\n}{}; s{//.*}{}; s{_bizarre}{}g; print O; } filter_finish(); } process_cargo_toml('Cargo.toml', 'tests/pub-export/bizarre-facade/Cargo.toml', 'src/lib.rs', '', ''); process_cargo_toml('macros/Cargo.toml', 'tests/pub-export/bizarre-macros/Cargo.toml', 'macros/macros.rs', < (manualal # book/**.md # unconditionally ensures a navbar at the top of every file # # The manually maintainer marker instructions # # IDENT identifies this navbar instance and ends up being compared with # IDENTs from maint/navbar. It need not be unique, here. # RELATIVE is . or .. or something, to go back to the rustdoc toplevel. # In the book, RELATIVE is absent and the book's own URL is used. # # RELATIVE can also be an absolute URL containing `@version@` which will be # replaced with the toplevel crate's actual version number. (This allows the # navbar at the top of our README.md, which is formatted in forges, crates.io, # etc., to refer to the correctg version, so the user doesn't get willy-nilly # teleported to a different version. (That still happens if they visit the # guide, which isn't version.ed) Some background: # https://github.com/rust-lang/crates.io/issues/10541 # # The file maint/navbar contains the contents of the navbar. # divider = raw HTML output between entries # book-to-rustdoc = URL for the rustdocs, from the book # text = raw HTML # | ends the entry and prepares to start a new one # url IDENT = URL starts an entry # generally produces # in an IDENT navbar itself, # the closing tag is done by | # If URL has no scheme, it is rel to rustdoc toplevel # IDENT in url should be unique. # subanchor IDENT = text # in a navbar instance IDENT itself, # IDENT should be different to any IDENTs in url # $$ indicates end of the entries # so that `text` is for the trailer # actually the instruction $ (see below) # Must be a single space around ` = `, since we want to be able to # control spaces. # Lines ending `$` have the `$` stripped off; the `$` protects trailing ws. # # Additionally, markdown defined anchors which look like this # [navbar-versioned-url-ANCHOR]: SOME-URL # have the URL replaced. The URL is computed from the first # URL in an @dd-navbar directive that contains @version@, # with /index.html#ANCHOR appended. This is used to provide a versioned # link to the overall TOC in running text in the README.md. use strict; use Carp; use Getopt::Long; use TOML; our $check; our $debug; our $found_differences = 0; our $toml; our $navbar_file = "maint/navbar"; GetOptions( "check" => \$check, "debug|D" => \$debug, ) or die("$0: bad usage\n"); our $file; our $html_comment_start_re = qr{^\Q"; $o .= ''; my $e; my $divider; my $book_to_rustdoc; open N, '<', $navbar_file or confess $!; while () { next if m{^\#}; next unless m{\S}; chomp; s{\s*$}{}; s{\$$}{}; if (m{^url (\S+) = (\S+)$}) { confess if defined $e; if ($d->{This} eq $1) { $o .= ""; $e = ""; } else { my $up = $d->{UpInRustdoc} // $book_to_rustdoc; my $url = $2; $url = $up . $2 unless $url =~ m{^\w+:}; $o .= sprintf '', $url; $e = ""; } } elsif (s{^text = }{}) { $o .= $_; } elsif (s{^divider = }{}) { $divider .= $_; } elsif (s{^book-to-rustdoc = }{}) { $book_to_rustdoc = $_; } elsif (s{^subanchor (\S+) = }{}) { if ($d->{This} eq $1) { $o .= "$_"; } else { $o .= $_; } } elsif (m{^[|\$]$}) { $o .= $e; $e = undef; $o .= $divider if $_ =~ m/\|/; } else { die "$navbar_file:$.: bad instruction in navbar\n"; } } N->error and confess $!; $o .= $e if defined $e; $e = undef; $o .= "\n"; return $o; } sub filter_rustdoc () { my $navbar_details; my $versioned_base; while () { my $orig_line = $_; my $line_prefix_bare = ''; my $line_prefix_spc = ''; if ($file =~ m{\.rs$}) { if (s{^(//[/!]) ?}{}) { $line_prefix_bare = "$1"; $line_prefix_spc = "$1 "; } else { die "$file:$.: navbar instruction at end of rustdoc\n" if $navbar_details; print O; next; } } if ($navbar_details) { my $navbar = $line_prefix_spc.make_navbar($navbar_details); if (m{$html_comment_start_re}) { $_ = $navbar; } elsif (!m/\S/ && m/\n/) { $_ = $navbar.$line_prefix_bare."\n"; } else { die "$file:$.: unexpected content after navbar instruction,". " wanted blank line or \n", $make_navbar->('book-chapter'), "\n", $_; } else { m{^\}) { $skip = 1; print $toc, "\n\n"; next; } if (!m{^ +\*}) { $skip = 0; } ' $file >$file.new if $install; then mv -f $file.new $file else set +e diff -u $file $file.new rc=$? set -e case $rc in 0) ;; 1) problems=true ;; *) exit $rc ;; esac fi } process_files if $problems; then echo 'Documentation TOC mismatch - rerun maint/update-tocs' exit 1 fi work/maint/update-versions0000775000000000000000000000240214763657303013132 0ustar #!/bin/bash # usage() { cat >&2 <<'END' Usage: maint/update-versions [VERSION] Updates versions to VERSION, if supplied, and then runs cargo fetch --offline # to update the lockfile maint/update-bizarre to refresh lockfiles etc. It may be necessary to update macros/semver.rs too - see template_export_semver_check_func_macro END } # We're not using cargo-edit or cargo-version because our # multi-version tests make things weird. # CARGO='nailing-cargo -Eu' maint/update-versions VERSION # CARGO='nailing-cargo -Eu' maint/update-versions set -e -o pipefail case "$#/$1" in 0/) ;; 1/1.*) ;; *) usage; exit 12;; esac version=$1 process () { perl -i~ -wpe ' use strict; my $eligible = qr{"([=^]?)(?!0\.0\.)\d+\.\d+\.\d+"}; my $new = "'"$version"'"; if (m{^version *= }) { s{$eligible}{"$1$new"}; } elsif (m{^derive-deftly.*.*path = }) { s{\bversion *= *$eligible}{version = "$1$new"}g; } ' "$1" } if [ "x$version" != x ]; then process Cargo.toml process macros/Cargo.toml process tests/Cargo.toml process tests/pub-export/pub-a/Cargo.toml process tests/stderr/Cargo.toml fi ${CARGO-cargo} fetch maint/update-minimal-versions maint/update-docs-navbars work/maint/via-cargo-install-in-ci0000775000000000000000000000075314763657303014322 0ustar #!/bin/bash set -euo pipefail badusage () { echo >&2 "$0: bad usage: $1"; exit 8; } case "$1" in -*) badusage 'first argument must be command (package)' ;; esac cache="$*" cmd="$1"; shift case "${1-}" in [^-]*) badusage 'subsequent arguments must be options to cargo install' ;; esac cache="${cache// /,}" cache="cache/$cache" if cp -v "$cache" "$CARGO_HOME"/bin/"$cmd"; then exit 0; fi mkdir -p cache cargo install --locked "$@" "$cmd" cp -v "$CARGO_HOME/bin/$cmd" "$cache" work/playground/0000775000000000000000000000000014763657303011132 5ustar work/playground/README.txt0000664000000000000000000000030714763657303012630 0ustar These are non-working examples, which are not compiled and tested. They were written to help with planning for possible new features. Many of those possible new features are dealt with in NOTES.md. work/playground/cross-product.rs0000664000000000000000000000160714763657303014313 0ustar //! THIS IS A NON-WORKING CONCEPT, FOR FUTURE DEVELOPMENT // This is a fanciful example of future possibilities! // // Example that makes a self-cross-product out of an enum, // (We only support unit enums without generics, for clarity.) // // This demonstrates use of the outer context facility. define_derive_deftly!{ Squared = // New expansions: // ${outer_variant ...} // ${outer_field ...} // Expands ... in the context of an outer iteration. // Can be nested to get an outer outer context. $tvis enum $[$ttype Squared] { ${for variants { ${for variants { $[ ${outer_variant $vname} $vname ], }} }} } } #[derive(Deftly)] #[derive_deftly(Squared)] enum Greek { Alpha, Beta, } // Expands to: enum GreekSquared { AlphaAlpha, AlphaBeta, BetaAlpha,, BetaBeta, } fn main(){ } work/playground/multi-driver.rs0000664000000000000000000000165414763657303014131 0ustar //! THIS IS A NON-WORKING CONCEPT, FOR FUTURE DEVELOPMENT // This is a fanciful example of future possibilities! // // Example that makes a union of any number of enums, // (We only support unit enums without generics, for clarity.) // // This demonstrates multiple drivers, with a template that // treats all drivers equivalent.y // // Before pursuing this we should also // - demonstrate a template that expects a fixed number of // drivers and treats them differently // - consider how to invoke a precanned template on multiple drivers // - figure out how even any of this will work underneath #[derive(Deftly)] enum Greek { Alpha, Beta, } #[derive(Deftly)] enum Colour { Red, Blue, } derive_deftly_adhoc!{ Greek, Colour: enum $[ ${for_inputs $tname} ] { ${for_inputs { $vname, }} }} } // Expands to: enum GreekColour { Alpha, Beta, Red, Blue, } work/rustfmt.toml0000664000000000000000000000001714763657303011345 0ustar max_width = 79 work/src/0000775000000000000000000000000014763657303007535 5ustar work/src/lib.rs0000664000000000000000000000561114763657303010654 0ustar // This is the "library" crate. // // It is conventional for proc macro crates to be wrapped up // in a library crate, which reexport the proc macros, mostly because // a proc macro crate cannot contain anything else. // // Currently our plans do not appear to include any items other than // proc macros. But we shouldn't foreclose that. So for now // this library crate ought to declare a dependency on the macro crate // and reexport the macros. // // Also this crate is where the docs live, right here as crate-level // docs. #![allow(clippy::style, clippy::complexity)] #![allow(rustdoc::invalid_html_tags)] // @derive-deftly invalid-html-tags #![doc=include_str!("../README.md")] // // We take some serious liberties with rustdoc's HTML, here. // We want this "overall TOC" to be an h1 heading // and we want it *not* to be within the div // that rustdoc puts this this whole toplevel docs in. // So we *close that div* and reopen a new one. //! //!
//! //!

§derive-deftly documentation, overall table of contents

//! //!
//! //! //! //! //!
//!
//!

//! // This next part is is also unwarranted chumminess with rustdoc. // Some time between Rust 1.85 and 1.86, rustdoc changed from using //
    and a pair of
    with , to this
    , which is better. // This does mean this doesn't render precisely right on Stable right now. // //!
    //!
    Guide
    //!
    Tutorial and walkthrough, in book form
    //!
    pub use derive_deftly_macros::derive_deftly_engine; pub use derive_deftly_macros::{ define_derive_deftly, derive_deftly_adhoc, template_export_semver_check, Deftly, }; // We (ab)use the module system as places to hang our documentation. #[deny(rustdoc::invalid_html_tags)] #[doc=include_str!("../doc/reference.md")] pub mod doc_reference {} #[deny(rustdoc::invalid_html_tags)] #[doc=include_str!("../doc/implementation.md")] pub mod doc_implementation {} #[deny(rustdoc::invalid_html_tags)] #[doc=include_str!("../CHANGELOG.md")] pub mod doc_changelog {} #[cfg(not(feature = "minimal-1"))] compile_error! { "You must enable (directly or indirectly) the derive-deftly crate feature `minimal-1`!" } work/tests/0000775000000000000000000000000014763657303010110 5ustar work/tests/Cargo.toml0000664000000000000000000000651114763657303012043 0ustar [package] name = "derive-deftly-tests" version = "0.0.1" edition = "2021" license="MIT" authors=["Ian Jackson ", "and the contributors to Rust derive-deftly"] description="An ergonomic way to write derive() macros" homepage = "https://gitlab.torproject.org/Diziet/rust-derive-deftly" repository = "https://gitlab.torproject.org/Diziet/rust-derive-deftly" publish = false [features] ui = [] recent = [] # We're not running with the MWRV compiler default = [] full = ["derive-deftly/full", "beta"] beta = ["derive-deftly/beta"] # We can't make macrotest, ui, recent and full default features because # in 1.54 (which is why we want this) --no-default-features doesn't work. # *Some* of the cargo features from derive-deftly/macros # need to be reproduced here. We don't need their actual consequences, # since the way we invoke the tests gets those via the dependency # on derive-deftly/full, above. case = [] meta-as-expr = [] meta-as-items = [] # # `expect` is not here because it doesn't work without a build.rs, # and anyway we don't test that functionality here. [dependencies] derive-deftly = { path = "..", version = "*", default-features = false, features = ["minimal-1"] } easy-ext = "1" # educe 0.5.0 to 0.5.10 inclusive are broken but cargo cannot express this # https://github.com/magiclen/educe/issues/14 # https://github.com/magiclen/educe/pull/15 educe = ">=0.4.6, <0.7" glob = "0.3" paste = "1" static_assertions = "1" toml = ">=0.5.0, <0.9" # Really, this should be regex 1.8 but only if feature "recent" is enabled. # Some of our tests in directly.rs and sub-modules require regex 1.8. # However, regex 1.8 is not compatuble with our MSRV. # This would be fine, since those tests don't run withotu feature "recent". # But! cargo can't do feature-dependent resolution. If we write this # dependency sa optional, 1.8, the Cargo.lock.minimal ends up using 1.8 # too - and there's no way to express that that's not what we want. # So this version requirement is too loose. # # This all works in practice because: # - for the minimal versions, the Cargo.lock.minimal chooses an old # regex, but we don't actually exercise the code paths that would # require new regex. # - for the main tests, our Cargo.lock ends up containing a recent # regex anyway. This is true of a fully regenerated lockfile too. # The alternative would be to use only older regex features everywhere, # or to have multiple lockfiles. # # If you do too much messing about with dependencies, the Cargo.lock # can end up in a state where we run these tests with regex < 1.8 # and then it breaks. See # https://gitlab.torproject.org/Diziet/rust-derive-deftly/-/issues/85 regex = "1" indexmap = ">=1.8, <3" itertools = ">=0.10.1, <0.16" proc-macro-crate = ">=1.1.3, <4" proc-macro2 = "1.0.53" quote = "1" sha3 = "0.10" strum = { version = ">=0.24, <0.28", features = ["derive"] } syn = { version = "2.0.53", features = ["extra-traits", "full"] } void = "1" heck = ">=0.4, <0.6" # TODO dh-rust: There should be a way to filter this out # We don't want to run macrotest tests in Debian. They use cargo expand. # macrotest = { version = "1.0.10", optional = true } trybuild = "1.0.46" [lib] path = "tests.rs" [[test]] name = "macrotest" path = "macrotest.rs" [[test]] name = "trybuild" path = "trybuild.rs" [[test]] name = "stderr" path = "stderr.rs" work/tests/build.rs0000664000000000000000000000160114763657303011553 0ustar // build.rs for tests // We make extensive use of cargo features, and compile various // of the same code in different cargo packages with different feature sets. // This means that cargo doesn't always know the features we're testing // really exist. Likewise with our `--cfg=derive_deftly_dprint`. // // Here, we tell rustc, via cargo, that these are all allowed. // This is a superset of macros/build.rs, in a more sophisticated style. use std::fmt::Write as _; #[allow(clippy::format_collect)] fn main() { let features = "bizarre expect skip_imported_driver" .split_ascii_whitespace() .map(|f| format!(r#""{}", "#, f)) .collect::(); let mut o = String::new(); let mut pc = |s| writeln!(o, "cargo::rustc-check-cfg=cfg({})", s).unwrap(); pc("derive_deftly_dprint"); pc(&format!("feature, values({})", features)); print!("{}", o); } work/tests/compat/0000775000000000000000000000000014763657303011373 5ustar work/tests/compat/README.md0000664000000000000000000000207514763657303012656 0ustar # Introduction These three crates test forward/backward compatibility with the version of derive-deftly prior at the most recent flag day. See also "Compatibility testing (and semver updates)" in `macros/HACKING.md`. ## Tests I. Forward compatibility (higher layer crate is more up to date) new-b -> new derive_deftly (bizarre version) | V old-a -> old derive_deftly This reuses pub-b.rs (which expects bizarre derive-deftly) and pub-a.rs. 2. Backward compatibility (lower layer crate is more up to date) old-b -> old derive_deftly | V pub-a -> current derive_deftly This reuses pub-a.rs (which expects vanilla derive-deftly) and a filtered version of pub-b.rs called old-b.rs. pub-b.rs expects bizarre derive-deftly but there is no published old version, so we have maint/update-bizarre filter out the _bizarres. ## Two sets of these tests There are `new-b`, `old-a` and `old-b`. These test compatibility of *template exports* only. There are also `edrv-new-b`, `edrv-old-a` and `edrv-old-b`. These test compatibility of exporting driver structs. work/tests/compat/edrv-new-b/0000775000000000000000000000000014763657303013341 5ustar work/tests/compat/edrv-new-b/Cargo.toml0000664000000000000000000000113214763657303015266 0ustar # See tests/compat/README.md [package] name = "edrv-new-b" version = "0.0.1" edition = "2021" license="MIT" authors=["Ian Jackson ", "and the contributors to Rust derive-deftly"] description="driver export compat testing - new part B, uses bizarre version" publish = false [dependencies] pub-a = { package = "edrv-old-a", path = "../edrv-old-a", version = "0.0.1" } derive-deftly = { package = "bizarre-derive-deftly", path = "../../pub-export/bizarre-facade", version = "0.0.666" } [lib] path = "../../pub-export/pub-b/pub-b.rs" doc = false doctest = false work/tests/compat/edrv-new-b/build.rs0000777000000000000000000000000014763657303022206 2../../pub-export/pub-b/build.rsustar work/tests/compat/edrv-old-a/0000775000000000000000000000000014763657303013325 5ustar work/tests/compat/edrv-old-a/Cargo.toml0000664000000000000000000000065114763657303015257 0ustar # See tests/compat/README.md [package] name = "edrv-old-a" version = "0.0.1" edition = "2018" license="MIT" authors=["Ian Jackson ", "and the contributors to Rust derive-deftly"] description="driver export compat testing - old part A" publish = false [dependencies] derive-deftly = { version = "=0.12.1" } [lib] path = "../../pub-export/pub-a/pub-a.rs" doc = false doctest = false work/tests/compat/edrv-old-b/0000775000000000000000000000000014763657303013326 5ustar work/tests/compat/edrv-old-b/Cargo.toml0000664000000000000000000000073214763657303015260 0ustar # See tests/compat/README.md [package] name = "edrv-old-b" version = "0.0.1" edition = "2018" license="MIT" authors=["Ian Jackson ", "and the contributors to Rust derive-deftly"] description="driver export compat testing - old part B" publish = false [dependencies] pub-a = { path = "../../pub-export/pub-a", version = "0.0.1" } derive-deftly = { version = "=0.12.1" } [lib] path = "../old-b/old-b.rs" doc = false doctest = false work/tests/compat/edrv-old-b/build.rs0000777000000000000000000000000014763657303022173 2../../pub-export/pub-b/build.rsustar work/tests/compat/new-b/0000775000000000000000000000000014763657303012403 5ustar work/tests/compat/new-b/Cargo.toml0000664000000000000000000000121514763657303014332 0ustar # See tests/compat/README.md [package] name = "new-b" version = "0.0.1" edition = "2021" license="MIT" authors=["Ian Jackson ", "and the contributors to Rust derive-deftly"] description="compat testing crates - new part B, uses bizarre version" publish = false [features] default = ["skip_imported_driver"] skip_imported_driver = [] [dependencies] pub-a = { package = "old-a", path = "../old-a", version = "0.0.1" } derive-deftly = { package = "bizarre-derive-deftly", path = "../../pub-export/bizarre-facade", version = "0.0.666" } [lib] path = "../../pub-export/pub-b/pub-b.rs" doc = false doctest = false work/tests/compat/old-a/0000775000000000000000000000000014763657303012367 5ustar work/tests/compat/old-a/Cargo.toml0000664000000000000000000000063514763657303014323 0ustar # See tests/compat/README.md [package] name = "old-a" version = "0.0.1" edition = "2018" license="MIT" authors=["Ian Jackson ", "and the contributors to Rust derive-deftly"] description="compat testing crates - old part A" publish = false [dependencies] derive-deftly = { version = "=0.12.1" } [lib] path = "../../pub-export/pub-a/pub-a.rs" doc = false doctest = false work/tests/compat/old-b/0000775000000000000000000000000014763657303012370 5ustar work/tests/compat/old-b/Cargo.toml0000664000000000000000000000101614763657303014316 0ustar # See tests/compat/README.md [package] name = "old-b" version = "0.0.1" edition = "2018" license="MIT" authors=["Ian Jackson ", "and the contributors to Rust derive-deftly"] description="compat testing crates - old part B" publish = false [features] default = ["skip_imported_driver"] skip_imported_driver = [] [dependencies] pub-a = { path = "../../pub-export/pub-a", version = "0.0.1" } derive-deftly = { version = "=0.12.1" } [lib] path = "old-b.rs" doc = false doctest = false work/tests/compat/old-b/old-b.rs0000664000000000000000000000237414763657303013741 0ustar //! Forward/backward compatibility tests //! See tests/compat/README.md #[cfg(not(feature = "skip_imported_driver"))] pub mod adhoc_template { use derive_deftly::derive_deftly_adhoc; pub trait NumFields { fn num_fields() -> usize; } use pub_a::a_driver::*; derive_deftly_adhoc! { pub_a::ADriver: impl<$tgens> NumFields for $ttype { #[allow(clippy::identity_op)] fn num_fields() -> usize { $( let _: $ftype; ) 0 + ${for fields { 1 }} } } } } pub mod b_driver { use derive_deftly::Deftly; pub struct BField(T); #[derive(Deftly)] #[derive_deftly(pub_a::IsEnum)] pub enum BDriver { Variant(BField), } use pub_a::derive_deftly_template_IsEnum as derive_deftly_template_ImportedIsEnum; #[derive(Deftly)] #[derive_deftly(ImportedIsEnum)] pub struct BDriver2; } #[test] fn invoke_template() { use pub_a::a_trait::IsEnum; assert!(b_driver::BDriver::<()>::is_enum().is_some()); } #[cfg(not(feature = "skip_imported_driver"))] #[test] fn invoke_exported_driver() { use adhoc_template::NumFields; assert_eq!(pub_a::a_driver::ADriver::<()>::num_fields(), 1); } work/tests/directly/0000775000000000000000000000000014763657303011727 5ustar work/tests/directly/check_approx_equal.rs0000664000000000000000000001073114763657303016134 0ustar //! Tests for `${approx_equal}` use super::*; #[test] fn check_approx_equal_lit() { // Testing all the corner cases for literals is quite hard. // negative literals are deeply weird, see // https://doc.rust-lang.org/reference/procedural-macros.html#declarative-macro-tokens-and-procedural-macro-tokens // so we must test this in a crazy way too. // // We can make single-token negative literals with // the constructors on proc_macro2::Literal, or with the FromStr impl. // Empirically, though, a proc_macro2::TokenStream *cannot* // contain negative literals! They seem to be converted on input. // So, anyway, we test comparisons of tokenstreams constructed // - by parsing a string // - by putting a single literal into a TS // (as returned by versions()), in all four combinations, // for each test case. fn versions(s: &str) -> impl IntoIterator { let s = s.trim(); let ts_direct: TokenStream = syn::parse_str(s).expect(s); if s.starts_with('-') { match ts_direct.clone().into_iter().next() { Some(TT::Punct(p)) if p.as_char() == '-' => {} other => panic!("{:?} => {:?}", s, other), } } let lit = proc_macro2::Literal::from_str(s).expect(s); let tt = TT::Literal(lit.clone()); let ts_from_tt: TokenStream = iter::once(tt).collect(); vec![ ("parse2::", ts_direct), ("Literal::from_str", ts_from_tt), ] } fn chk(s: &str) { for t in s.split('\n').map(|t| t.trim()).filter(|t| !t.is_empty()) { eprintln!("{}", t); let (l, r) = t.rsplit_once('=').unwrap(); let mut l = l.chars(); let o = l.next_back().unwrap(); let l = l.as_str(); let exp = match o { '=' => Ok(Equality::Equal), '!' => Ok(Equality::Different), '?' => Err(()), other => panic!("{:?}", other), }; for (lv, l) in versions(l) { for (rv, r) in versions(r) { eprintln!("{} {} {}", t, lv, rv); let got = tokens_cmpeq(l.clone(), r, Span::call_site()) .map_err(|_| ()); assert_eq!(got, exp); } } } } // literals; they are compared by their string representation // str chk(r#" "hi" != "ho" "hi" == "h\x69" "hi"x ?= "hi" "hi" == r"hi" "hi" != b"hi" "#); // byte str chk(r#" b"hi" != b"ho" b"hi" == b"h\x69" b"hi"x ?= b"hi" b"hi" == br"hi" b"i" != 'i' b"i" != b'i' "#); // char chk(r#" 'i' != 'o' 'i' == 'i' 'i' == '\x69' 'i'x ?= 'i' 'i' != b'i' "#); // byte chk(r#" b'i' != b'o' b'i' == b'i' b'i' == b'\x69' b'i'x ?= b'i' b'i' != 0x69 "#); // int chk(r#" 1 != 2 1 != -1 1 == 01 1 == 0o1 1 == 0b1 1 == 0x1 1 == 1_u32 1u32 == 1_u32 -1u32 == -1_u32 0xffff_ffff_ffff_ffff == 0xffff_ffff_ffff_ffff -0xffff_ffff_ffff_ffff == -0xffff_ffff_ffff_ffff 0x7777_1234_5678_1234_5678_1234_5678_1234_5678 != 1 0x1234_5678_1234_5678_1234_5678_1234_5678 ?= 0x1234_5678_1234_5678_1234_5678_1234_5678 "#); // float // Rust has no non-finite floating point literals, // so we don't need to test NaN or Inf chk(r#" 32 != 32.0 32.0 == 32.0 -32.0 == -32.0 32. != 32.0 32.0_f32 != 32.0f32 32.0 != 32.0_f32 -32.0 != -32. 0.0 != 0.00 0.0 != -0.00 "#); } #[test] fn check_approx_equal_lit_verbatim() { let span = Span::call_site(); let verb = syn::Lit::Verbatim("1".parse().unwrap()); let num = syn::Lit::Int(syn::LitInt::new("-1", span)); for (a, b) in [ // (&verb, &verb), (&verb, &num), (&num, &verb), ] { let r = macros::approx_equal::syn_lit_cmpeq_approx( a.clone(), b.clone(), &(span, "dummy"), ); assert!(r.expect_err("unsupp").to_string().contains("unsupported")); } } work/tests/directly/check_details.rs0000664000000000000000000002671414763657303015071 0ustar //! Tests of individual expansion details use super::*; use ExpectedOutputNoneGrouping as EONG; fn tests_using_driver( driver: TokenStream, grouping: ExpectedOutputNoneGrouping, cases: &[(&str, Result<&str, &str>)], ) { eprintln!("----- driver -----\n{}\n----- cases -----", driver); let driver: syn::DeriveInput = syn::parse2(driver).unwrap(); for (input_s, exp) in cases { eprintln!(" case {}", input_s); let input: Template<_> = syn::parse_str(input_s).unwrap(); let exp = ExpectedOutput::parse(*exp, grouping).unwrap(); let got = Context::call(&driver, &parse_quote!(crate), None, |ctx| { let mut ts = TokenAccumulator::new(); input.expand(&ctx, &mut ts); ts.tokens() }); exp.compare(got, input_s).unwrap_or_else(|m| { m.eprintln_initial(); m.eprintln_extended(); panic!("test case mismatch"); }); } } fn check_template_parse_fail(template: &str, exp: &str) { eprintln!("check parsing failure for {:?}", template); let got = syn::parse_str::>(template) .unwrap_err() .to_string(); assert!(got.contains(exp), "expected {:?}, got {:?}", exp, got); } #[test] fn check_details_one() { tests_using_driver( quote!( struct S { f: Option, } ), EONG::Explicit, &[ // ("$vpat", Ok("S { f: f_f, }")), ("$( $ftype )", Ok("« Option:: »")), ], ); } #[test] fn check_details_idents() { tests_using_driver( quote! { #[deftly(raw = "r#for", plain = "plain")] struct S; }, EONG::Explicit, &[ ("${tmeta(raw) as str}", Ok(r#""r#for""#)), ("${tmeta(raw) as ident}", Ok("r#for")), ("$< ${tmeta(raw)} >", Ok("r#for")), ( "$< ${tmeta(plain)} _ ${tmeta(raw)} >", Err(r#""plain_r#for""#), ), ("$< ${tmeta(raw)} _ ${tmeta(plain)} >", Ok("r#for_plain")), ("${tmeta(raw) as ident}", Ok("r#for")), ("$< ${tmeta(raw) as ident} >", Ok("r#for")), ( "$< ${tmeta(plain)} _ ${tmeta(raw) as ident} >", Ok("plain_for"), ), ( "$< ${tmeta(raw) as ident} _ ${tmeta(plain)} >", Ok("for_plain"), ), ], ); } #[test] fn check_details_meta() { tests_using_driver( quote!( #[deftly(ty = "Option", expr = "42", r#mod = "mod m {}")] struct S; ), EONG::Explicit, &[ // ("${tmeta(expr) as str}", Ok(r#""42""#)), #[cfg(feature = "meta-as-expr")] ("${tmeta(expr) as expr}", Ok("(42)")), // Note that we add the missing colons here, so that you // can use use this in UFCS etc. ("${tmeta(ty) as ty}", Ok("« Option:: »")), ("${tmeta(ty) as path}", Ok("« Option:: »")), // `as token_stream` gives you precisely what's in the driver ("${tmeta(ty) as token_stream}", Ok("Option")), // You can look up things with raw identifiers #[cfg(feature = "meta-as-items")] ("${tmeta(r#mod) as items}", Ok("mod m {}")), ("${tmeta(r#mod) as token_stream}", Ok("mod m {}")), // This one is rather odd, but comparison of Idents // considers rawness, in proc_macro::Ident and in syn::Path. #[cfg(feature = "meta-as-expr")] ("${tmeta(r#expr) as str}", Err("no value in data structure")), ], ); #[cfg(not(feature = "meta-as-expr"))] check_template_parse_fail( "${tmeta(expr) as expr}", "feature meta-as-expr disabled", ); #[cfg(not(feature = "meta-as-items"))] check_template_parse_fail( "${tmeta(r#mod) as items}", "feature meta-as-items disabled", ); } /// Returns `Ok(s)`, but checking that it parses as an item fn item_ok(s: &str) -> Result<&str, &str> { syn::parse_str(s) .map(|_: syn::Item| s) .map_err(|e| panic!("parse failed: {:?}: {}", s, e)) } // Test "redefinition keywords" and `$vpat` // // In particular, we test that // - you can define a new data structure // - of every kind // - with >1 of every kind of content (field, variant) // (proving that the commas are all there) // - and that it parses as an item // This shows that our commas are all present and correct, // avoiding a repetition of #74 for these cases. // We use `Flatten` because any groups are buried in the middle, here. // // We also have handwritten expected outputs for $vpat in all cases. #[test] #[rustfmt::skip] // this test includes deliberately-missing commas in inputs fn check_details_shapes() { let redefine = r#" $tdefkwd $tdeftype ${tdefvariants $( ${vdefbody $vname $( ${fdefine $fname} $ftype, )} )} "#; tests_using_driver( quote!( struct S<'l, G: Bound = Def>; ), EONG::Flatten, &[ ("$vpat", Ok("S {}")), (redefine, item_ok("struct S<'l, G: Bound = Def>;")), ], ); tests_using_driver( quote!( struct S(A, B); ), EONG::Flatten, &[ ("$vpat", Ok("S { 0: f_0, 1: f_1, }")), (redefine, item_ok("struct S(A, B,);")), ], ); tests_using_driver( quote!( struct S { a: A, b: B } ), EONG::Flatten, &[ // ("$vpat", Ok("S { a: f_a, b: f_b, }")), (redefine, item_ok("struct S { a: A, b: B, }")), ], ); tests_using_driver( quote!( enum E { U, T(A, B), S { a: A, b: B } } ), EONG::Flatten, &[ ("$( $vpat | )", Ok(r"E::U {} | E::T { 0: f_0, 1: f_1 ,} | E::S { a: f_a, b: f_b, } |")), ("$( ${vpat self=$ vname=$ fprefix=g_} | )", Ok(r"TE::VU {} | TE::VT { 0: g_0, 1: g_1, } | TE::VS { a: g_a, b: g_b, } |")), (redefine, item_ok(r"enum E { U, T(A, B,), S { a: A, b: B, }, }")), ], ); } #[test] fn check_details_types() { tests_using_driver( quote! { struct T<'l0, 'l1: 'l0, AG: Bound = Def>(A<'l0, AG>) where AG: 'l0; }, EONG::Explicit, &[ // Note lack of final `,` in generics inside A and T, below. // That's fine because one can't deconstruct this; // if we invent $fgens or something, it would need the `,` ("$( $ftype )", Ok("« A::<'l0, AG> »")), ("$ttype", Ok("T::<'l0, 'l1, AG>")), ("$vtype", Ok("T::<'l0, 'l1, AG>")), ("${vtype self=T2 vname=$}", Ok("T2")), ( "${vtype self=$ vname=$}", Ok("TT::<'l0, 'l1, AG>"), ), ("$tdeftype", Ok("T<'l0, 'l1: 'l0, AG: Bound = Def>")), // However, "gens" all have a final comma unless they're empty. ("$tgens", Ok("'l0, 'l1: 'l0, AG: Bound,")), ("$tgnames", Ok("'l0, 'l1, AG,")), ("$twheres", Ok("AG: 'l0,")), ("$tdefgens", Ok("'l0, 'l1: 'l0, AG: Bound = Def,")), ], ); #[cfg(feature = "recent")] // const generics tests_using_driver( quote! { struct T<'l, G, const C: i32 = 0>(); }, EONG::Explicit, &[ // No commas in non-deconstructed type names ("$ttype", Ok("T::<'l, G, C>")), ("$vtype", Ok("T::<'l, G, C>")), ("${vtype self=$}", Ok("TT::<'l, G, C>")), ("$tdeftype", Ok("T<'l, G, const C: i32 = 0>")), // However, "gens" all have a final comma unless they're empty. ("$tgens", Ok("'l, G, const C: i32,")), ("$tgnames", Ok("'l, G, C,")), ("$tdefgens", Ok("'l, G, const C: i32 = 0,")), ], ); tests_using_driver( quote! { struct Unit; }, EONG::Explicit, &[ ("$tgens", Ok("")), ("$tgnames", Ok("")), ("$twheres", Ok("")), ("$tdefgens", Ok("")), ], ); tests_using_driver( quote! { enum E<'l0, 'l1: 'l0, AG: Bound> { U } }, EONG::Explicit, &[ // ("$( $vtype ) /", Ok("E::U::<'l0, 'l1, AG> /")), ( "$( ${vtype self=$ vname=$} ) /", Ok("TE::VU::<'l0, 'l1, AG> /"), ), ], ); #[cfg(feature = "recent")] // const generics tests_using_driver( quote! { enum E<'l, G, const C: i32 = 0> { U } }, EONG::Explicit, &[ // ("$( $vtype ) /", Ok("E::U::<'l, G, C> /")), ( "$( ${vtype self=$ vname=$} ) /", Ok("TE::VU::<'l, G, C> /"), ), ], ); tests_using_driver( quote! { enum E<'l0, 'l1: 'l0, AG: Bound, BG> { U, S { a: A<'l0, AG>, b: B::<'l1, BG>, }, } }, // Proper grouping is tested in the first block, above. EONG::Flatten, &[ // Again, note lack of final `,` inside `$ftype` and `$vtype` ("$( $ftype / )", Ok("A::<'l0, AG> / B::<'l1, BG> /")), ( "$( $vtype / )", Ok("E::U::<'l0, 'l1, AG, BG> / E::S::<'l0, 'l1, AG, BG> /"), ), ], ); } #[test] fn check_misplaced_when() { let chk = |s| { check_template_parse_fail( s, "${when } must be at the top-level of a repetition, before other content", ); }; chk(r#" ${for fields { a ${when true} }} "#); chk(r#" a ${when true} "#); chk(r#" ${if approx_equal({}, ${when true}) {}} "#); } #[test] fn check_redefine() { tests_using_driver( quote! { struct S; }, EONG::Explicit, &[ ("${define X 1} $X", Ok("1")), ("${define X 1} $X ${define X 2} $X", Ok("1 2")), ("${define X 1} $X { ${define X 2} $X } $X", Ok("1 { 2 } 1")), ( r#" ${define Y $X} ${define X 1} $X $Y ${define X 2} $X $Y "#, Ok("1 1 2 2"), ), ("${define X $X} $X", Err("recursive user-defined expansion")), ], ); } #[test] fn check_paste() { tests_using_driver( quote! { struct S; }, EONG::Explicit, &[ (r#"${define P { "a" $tname }} $P"#, Ok(r#""a" S"#)), (r#"${paste "a" $tname }"#, Ok(r#"aS"#)), (r#"${define P $< "a" $tname >} $< $P >"#, Ok(r#"aS"#)), (r#"${define P { $< "a" $tname >}} $< $P >"#, Ok(r#"aS"#)), ], ); } work/tests/directly/check_examples/0000775000000000000000000000000014763657303014702 5ustar work/tests/directly/check_examples/conditions.rs0000664000000000000000000001415114763657303017423 0ustar //! Examples involving searching over possible outputs for a particular input //! //! Where the input file doesn't specify //! which driver (or part of driver) generates the output. //! (Or maybe where it limits it according to `limit`). use super::*; pub struct ConditionExample { loc: DocLoc, /// A derive-deftly condition input: String, /// Which contexts give true true_contexts: Vec<(String, Limit)>, } #[derive(Default)] struct Tracker { ctxs: Vec, } struct TrackerEntry { context_desc: String, limvals: Vec>, got: Result, } impl Example for ConditionExample { fn print_checking(&self) { println!("checking :{} {}", self.loc, &self.input,); } fn check(&self, errs: &mut Errors, drivers: &[syn::DeriveInput]) { let cond = match syn::parse_str::>(&self.input) { Ok(y) => y, Err(e) => { errs.wrong(self.loc, format_args!("failed to parse: {}", e)); return; } }; let mut tracker = Tracker::default(); for_every_example_context(drivers, |ctx| { Ok::<_, Void>(self.check_one_context(&cond, &mut tracker, ctx)) }) .void_unwrap(); let problems = self.problems(&tracker); if problems.is_empty() { return; } eprintln!(); eprintln!("========================================"); for problem in problems { errs.wrong( self.loc, format_args!("example mismatch: {}", problem), ); } eprintln!("condition: {}", &self.input); let head_w = tracker .ctxs .iter() .map(|te| te.context_desc.len()) .max() .unwrap(); eprint!("{:l$} ", r#"context: \ exp. True for:"#, l = head_w); for (lim_s, _lim) in &self.true_contexts { eprint!(" {:1}", lim_s); } eprintln!(" Actual:"); for te in &tracker.ctxs { eprint!(" {:l$} ", &te.context_desc, l = head_w); for ((lim_s, _lim), lv) in izip!(&self.true_contexts, &te.limvals) { eprint!( " {:l$}", match lv { Ok(false) => '-', Ok(true) => 'y', Err(()) => 'E', }, l = lim_s.len() ); } eprintln!( " {}", match &te.got { Ok(false) => "-".into(), Ok(true) => "y".into(), Err(e) => format!("error: {}", e), } ); } eprintln!("========================================"); } } impl ConditionExample { pub fn new( loc: DocLoc, input: String, true_contexts: Vec<(String, Limit)>, ) -> Self { ConditionExample { loc, input, true_contexts, } } fn check_one_context( &self, cond: &Subst, tracker: &mut Tracker, ctx: &Context<'_>, ) { let context_desc = ctx.desc_for_tests(); let got = cond.eval_bool(ctx); let limvals = self .true_contexts .iter() .map(|(_lim_s, lim)| lim.matches(ctx)) .collect_vec(); tracker.ctxs.push(TrackerEntry { context_desc, got, limvals, }) } // for each Limit // // must be at least one ctx matches (Ok(true)) // must be at least one ctx positively non-matching Ok(false) // // for every ctx that matches // evaluation must produce true (not err) // // for each ctx // // if evaluation was true // must be at least one Limit that matches // // if evaluation was false // must be at no Limit that positively matches // must be at least one Limit that positively non-matches // // if evaluation was error // no Limit matches (handled above) fn problems(&self, tracker: &Tracker) -> Vec { let mut problems = vec![]; for (i, (lim_s, _lim)) in self.true_contexts.iter().enumerate() { if !tracker.ctxs.iter().any(|te| te.limvals[i] == Ok(true)) { problems.push(format!("{:?} matched no context", lim_s)); } if !tracker.ctxs.iter().any(|te| te.limvals[i] == Ok(false)) { problems.push(format!("{:?} dismatched no context", lim_s)); } } for te in &tracker.ctxs { let lim_results = izip!(&self.true_contexts, &te.limvals); match te.got { Ok(true) => { if !te.limvals.iter().any(|l| matches!(l, Ok(true))) { problems.push(format!( r#"for {}, evaluated to True, but not in list of "True for""#, &te.context_desc, )); } } Ok(false) => { if let Some(((lim_s, _), _)) = lim_results .clone() .find(|((_lim_s, _lim), lv)| matches!(lv, Ok(true))) { problems.push(format!( "for {}, evaluated to False, but listed as True for {:?}", &te.context_desc, lim_s, )); } if !lim_results .clone() .any(|((_lim_s, _lim), lv)| matches!(lv, Ok(false))) { problems.push(format!( r#"for {}, evaluated to False, but none of the "True for" excluded it"#, &te.context_desc, )); } } Err(_) => { // handled above } } } problems } } work/tests/directly/check_examples/contexts.rs0000664000000000000000000001313314763657303017120 0ustar //! Contexts (drivers and parts) and contextual limits use super::*; pub fn for_every_example_context( drivers: &[syn::DeriveInput], mut call: impl FnMut(&Context<'_>) -> Result<(), E>, ) -> Result<(), E> { for driver in drivers { Context::call(driver, &parse_quote!(crate), None, |ctx| { Ok::<_, syn::Error>((|| { call(&ctx)?; ctx.for_with_within::(|ctx, _| { call(ctx)?; ctx.for_with_within::(|ctx, _| { call(ctx) }) }) })()) }) .unwrap()?; } Ok(()) } /// Limit on the contexts (in the example structs) that this test is for /// /// This was (at an early stage) done with derive-deftly conditions. /// However, derive-deftly conditions throw an error when applied /// in contexts that lack all the information /// (for example, `$fname` outside a field). /// Also, they make it difficult to analyse what `Others` means. #[derive(Educe, Clone)] #[educe(Debug)] pub enum Limit { True, DdCond(Rc>), Name(String), Field { f: String, n: String }, Fields { n: String }, Others(#[educe(Debug(ignore))] Vec), } #[ext(ContextExt)] pub impl Context<'_> { fn vname_s(&self) -> Option { Some(self.variant?.variant?.ident.to_string()) } fn fname_s(&self) -> Option { let span = Span::call_site(); Some(self.field?.fname(span).to_token_stream().to_string()) } fn desc_for_tests(&self) -> String { let mut out = format!("{}", self.top.ident); if let Some(vname) = self.vname_s() { write!(out, "::{}", vname).unwrap(); } if let Some(fname) = self.fname_s() { write!(out, ".{}", fname).unwrap(); } out } } impl Limit { // Some tests aren't meaningful in some context. // Eg, we have "for" clauses that check the variant // kind (unit, tuple, named fields). That isn't // meaningful if we're in an enum and not in a variant. // d-d gives an error there. That makes this return `Err`. pub fn matches(&self, ctx: &Context<'_>) -> Result { let tname = |n| &ctx.top.ident.to_string() == n; let vname = |n| ctx.vname_s().map(|s| &s == n) == Some(true); let fname = |n| ctx.fname_s().map(|s| &s == n) == Some(true); Ok(match self { Limit::True => true, Limit::DdCond(cond) => cond.eval_bool(ctx).map_err(|_| ())?, Limit::Name(n) => tname(n) || vname(n) || fname(n), Limit::Field { f, n } => fname(f) && (tname(n) || vname(n)), Limit::Fields { n } => { ctx.field.is_some() && (tname(n) || vname(n)) } Limit::Others(v) => { // If we've had any `for field in Type` annotations, // we should skip any contexts that don't have a field. // Since, in that case, "other" means *other fields* if v.iter().any(|l| matches!(l, Limit::Field { .. })) { if ctx.fname_s().is_none() { return Ok(false); } } !v.iter().any(|l| { l.matches(ctx) // Treat errors in the others as "no match" // so if they might match "others" .unwrap_or_default() }) } }) } } type LimitViaDaCond = dyn Fn(()) -> SubstDetails; /// Table mapping limit regexp to derive-deftly `SubstDetails` // Breaking this out here allows us to format it nicely #[rustfmt::skip] const LIMIT_DD_COND_REGEXPS: &[(&str, &'static LimitViaDaCond)] = &[ (r"^structs?$", &SD::is_struct as _ ), (r"^enum( variant)?s?$", &SD::is_enum as _ ), (r"^braced (?:(?:struct|variant)s?)?$", &SD::v_is_named as _ ), (r"^tuple( variant)?s?$", &SD::v_is_tuple as _ ), (r"^unit( variant)?s?$", &SD::v_is_unit as _ ), ]; impl Limit { pub fn parse( for_: &str, all_must_match: &mut bool, others: Option<&mut Vec>, ) -> Result { use Limit as L; let dd_cond = |mk_sd: &dyn Fn(_) -> _| { L::DdCond(Rc::new(Subst { kw_span: Span::call_site(), sd: mk_sd(()), })) }; let limit = if let Some(mk_sd) = LIMIT_DD_COND_REGEXPS .iter() .cloned() .find_map(|(re, mk_sd)| m!(for_, re).then(|| mk_sd)) { dd_cond(mk_sd) } else if let Some((n,)) = mc!( for_, r"^`(?:struct |Enum::)?(\w+)(?:|\;|\(\.\.\.\)\;| \{\.\.\.\})`$", ) { L::Name(n.into()) } else if let Some((f, n)) = mc!(for_, r"^`(\w+)` in `(\w+)`$") { *all_must_match = true; L::Field { f, n } } else if let Some((n,)) = mc!(for_, r"^fields in `(\w+)`$") { *all_must_match = true; L::Fields { n } } else if m!(for_, "^others$") { *all_must_match = true; let others = others .ok_or_else(|| format!(r#""for others" not allowed here"#))?; return Ok(L::Others(mem::take(others))); } else { return Err(format!(r#"unhandled for clause "{}""#, for_)); }; if let Some(others) = others { others.push(limit.clone()); } Ok(limit) } } work/tests/directly/check_examples/for_toplevels_concat.rs0000664000000000000000000000475114763657303021471 0ustar //! Blockquote examples involving expanding for certain toplevels use super::*; pub struct ForToplevelsConcatExample { pub loc: DocLoc, pub input: String, pub toplevels: Vec, pub output: String, } impl Example for ForToplevelsConcatExample { fn print_checking(&self) { println!("checking :{} blockquotes input/output", self.loc); } fn check(&self, errs: &mut Errors, drivers: &[syn::DeriveInput]) { let (got, exp) = match (|| { let mut got = TokenStream::new(); let template: Template = syn::parse_str(&self.input) .map_err(|e| format!("parse template: {}", e))?; let exp: TokenStream = syn::parse_str(&self.output) .map_err(|e| format!("parse expected output: {}", e))?; for toplevel in &self.toplevels { (|| { let driver = drivers .iter() .find(|d| d.ident == toplevel) .ok_or_else(|| format!("driver not found"))?; let crate_ = &parse_quote!(crate); got.extend( Context::call(driver, crate_, None, |ctx| { let mut out = TokenAccumulator::new(); template.expand(&ctx, &mut out); out.tokens() }) .map_err(|e| format!("expansion failed: {}", e))?, ); Ok::<_, String>(()) })() .map_err(|e| format!("toplevel {}: {}", toplevel, e))?; } Ok::<_, String>((got, exp)) })() { Ok(y) => y, Err(m) => { errs.wrong(self.loc, format_args!("example failed: {}", m)); return; } }; let got = flatten_none_groups(got); let err = match check_expected_actual_similar_tokens(&exp, &got) { Err(e) => e, Ok(()) => return, }; eprintln!("=============================="); errs.wrong(self.loc, format_args!("example expansion mismatch:")); eprintln!("expanded for: {}", self.toplevels.join(", ")); err.eprintln(""); eprintln!("----- input -----\n{}", self.input.trim_end()); eprintln!("----- documented -----\n{}", self.output.trim_end()); eprintln!("=============================="); } } work/tests/directly/check_examples/possibilities.rs0000664000000000000000000001320214763657303020130 0ustar //! Examples involving searching over possible outputs for a particular input //! //! Where the input file doesn't specify //! which driver (or part of driver) generates the output. //! (Or maybe where it limits it according to `limit`). use super::*; pub struct PossibilitiesExample { loc: DocLoc, /// A derive-deftly template fragment input: TokenStream, /// Limit on which contexts to consider limit: Limit, /// Expected output output: ExpectedOutput, /// Are *all* the possibilities (subject to `limit`) supposed to match? all_must_match: bool, } struct Tracker { all_must_match: bool, matching_outputs: usize, other_outputs: Vec, skipped_context_descs: Vec, } impl Tracker { fn finish_ok(&self) -> Result<(), String> { if self.all_must_match { if !self.other_outputs.is_empty() { return Err( "at least one actual output doesn't match the documented output".into() ); } } if self.matching_outputs == 0 { return Err( "documented output does not match any of the actual outputs" .into(), ); } Ok(()) } fn should_continue(&self) -> bool { if !self.all_must_match && self.matching_outputs != 0 { return false; } true } fn note(&mut self, matched: Result<(), Mismatch>) { match matched { Ok(()) => self.matching_outputs += 1, Err(got) => self.other_outputs.push(got), } } fn note_skip(&mut self, context_desc: String) { self.skipped_context_descs.push(context_desc); } } impl Example for PossibilitiesExample { fn print_checking(&self) { println!("checking :{} {} => {}", self.loc, &self.input, self.output,); } fn check(&self, errs: &mut Errors, drivers: &[syn::DeriveInput]) { let mut tracker = Tracker { all_must_match: self.all_must_match, matching_outputs: 0, other_outputs: vec![], skipped_context_descs: vec![], }; //println!(" LIMIT {:?}", &self.limit); for_every_example_context(drivers, |ctx| { self.compare_one_output(&mut tracker, &ctx); Ok::<_, Void>(()) }) .void_unwrap(); match tracker.finish_ok() { Ok(()) => {} Err(m) => { eprintln!(); eprintln!("========================================"); errs.wrong(self.loc, "example mismatch"); eprintln!( r"{} input: {} limit: {:?} documented: {}", m, self.input, self.limit, self.output, ); for got in &tracker.other_outputs { got.eprintln_initial(); } eprintln!("matched: {}", tracker.matching_outputs); eprint!("skipped:"); for skip in tracker.skipped_context_descs { eprint!(" [{}]", skip); } eprintln!(""); for got in &tracker.other_outputs { got.eprintln_extended(); } eprintln!("========================================"); } } } } impl PossibilitiesExample { pub fn new( loc: DocLoc, input: &str, limit: Limit, all_must_match: bool, output: Result<&str, &str>, grouping: ExpectedOutputNoneGrouping, ) -> Result, String> { let input = parse_str(input, "input")?; let output = ExpectedOutput::parse(output, grouping)?; Ok(Box::new(PossibilitiesExample { loc, input, limit, all_must_match, output, })) } fn compare_one_output(&self, tracker: &mut Tracker, ctx: &Context<'_>) { if !tracker.should_continue() { return; }; let limit = &self.limit; let context_desc = ctx.desc_for_tests(); if !limit .matches(ctx) // Treat errors "skip". .unwrap_or_default() { //println!(" INAPPLICABLE {:?}", &context_desc); tracker.note_skip(context_desc); return; } let input = &self.input; let mut out = TokenAccumulator::new(); let got = (|| { let template: Template = syn::parse2(input.clone())?; template.expand(ctx, &mut out); let got = out.tokens()?; Ok(got) })(); let matched = self.output.compare(got, &context_desc); tracker.note(matched); } } #[test] fn poc() { let driver: syn::DeriveInput = parse_quote! { pub(crate) enum Enum<'a, 'l: 'a, T: Display = usize, const C: usize = 1> where T: 'l, T: TryInto { UnitVariant, TupleVariant(std::iter::Once::), NamedVariant { field: &'l &'a T, field_b: String, field_e: >::Error, }, } }; let input = quote! { $($vname,) }; let limit = Limit::Name("Enum".into()); let output = ExpectedOutput::parse( Ok("UnitVariant, TupleVariant, NamedVariant,"), ExpectedOutputNoneGrouping::Flatten, ) .unwrap(); PossibilitiesExample { all_must_match: false, loc: 42, input: input, limit: limit, output, } .check(&mut Errors::new(), &[driver]); } work/tests/directly/check_examples/reference_extract.rs0000664000000000000000000005357414763657303020756 0ustar //! Extract examples from the reference manual use super::*; use std::cell::Cell; #[derive(Debug)] enum InputItem { Bullet { loc: DocLoc, bullet: String, }, BlockQuote { loc: DocLoc, #[allow(dead_code)] // we ignore this for now options: String, content: String, }, Heading { #[allow(dead_code)] // we don't syntax check our headings loc: DocLoc, /// number of hashes depth: usize, text: String, }, Paragraph { loc: DocLoc, #[allow(dead_code)] // paragraphs content is not currently used content: String, }, Directive { used: DirectiveTrackUsed, loc: DocLoc, d: InputDirective, }, } #[derive(Debug)] enum InputDirective { For { for_: String, }, Structs {}, ForToplevelsConcat { toplevels: Vec, }, PossibilitiesBlockquote { heading_picture_loc: DocLoc, heading_picture: String, }, } type Preprocessed = Vec; use InputDirective as ID; use InputItem as II; #[derive(Default, Debug)] struct DirectiveTrackUsed(Cell); impl DirectiveTrackUsed { fn note(&self) { self.0.set(true); } fn is(&self) -> bool { self.0.get() } } fn read_preprocess(errs: &mut Errors) -> Preprocessed { let _ = dbg!(std::env::current_dir()); let file = File::open(format!("../{INPUT_FILE}")).unwrap(); let is_directive = |l: &str| m!(l, r"\s*") .unwrap_or_else(|| bail(loc, "directive suffix wrong")); let used = Default::default(); let d = if m!(l, "^-ignore$") { while let Some((loc, l)) = lines.next() { if m!(l, "^\n$") { break; } else if is_directive(&l) { errs.wrong(loc, "directive in ignore section"); break; } else { drop(l); } } None } else if let Some((for_,)) = mc!(l, r"^-for (\S.*)$") { let for_ = for_.trim_end().to_owned(); Some(ID::For { for_ }) } else if let Some((toplevels,)) = mc!(l, r"^-for-toplevels-concat (\S.*)$",) { let toplevels = toplevels .split_ascii_whitespace() .map(|s| s.to_owned()) .collect_vec(); Some(ID::ForToplevelsConcat { toplevels }) } else if m!(l, "^-structs") { Some(ID::Structs {}) } else if m!(l, "^-possibilities-blockquote") { (|| { let (p_loc, l) = lines.next()?; let (intro, field1, rest) = mc!(l, r"^(") .or_else(|| { errs.wrong(loc, "possibilities-blockquote not followed by table picture"); None })?; Some(ID::PossibilitiesBlockquote { heading_picture_loc: p_loc, heading_picture: format!( "{}{}{}", field1.repeat(intro.len()), field1, rest, ), }) })() } else { errs.wrong(loc, format_args!("unrecgonised directive: {l:?}")); continue; }; d.map(|d| II::Directive { used, loc, d }) } else if m!(l, "^\n$") { None } else { current_paragraph.get_or_insert((loc, String::new())).1 += &l; continue; }; end_paragraph!(); preprocessed.extend(ii); } end_paragraph!(); preprocessed } fn extract_structs(input: &Preprocessed) -> Vec { fn parse_content(loc: DocLoc, content: &str) -> Vec { let content = re!(r"(?m)^#(?: |$)").replace_all(content, ""); let items: Concatenated = syn::parse_str(&content) .unwrap_or_else(|e| { bail( loc, format_args!( "structs content parse failed: {e}, given {content}", ), ) }); items .0 .into_iter() .filter_map(|item| match item { syn::Item::Union(_) | syn::Item::Struct(_) | syn::Item::Enum(_) => Some( syn::parse2(item.into_token_stream()) .expect("failed to reparse item as DeriveInput"), ), _ => None, }) .collect_vec() } blockquotes_after_directive(input, |d| match d { ID::Structs {} => Some(()), _ => None, }) .map(|(_d_loc, (), bq_loc, content)| { parse_content(bq_loc, content).into_iter() }) .flatten() .map(|mut derive_input| { let mut seen_d_d = false; derive_input.attrs.retain(|attr| { seen_d_d || (|| { // IEFI simulates a long if let && chain matches!(attr.style, syn::AttrStyle::Outer) .then_some(())?; attr.path().is_ident("derive").then_some(())?; let derives = attr .call_in_parens( macros::meta::PreprocessedValueList::parse_inner, ) .ok()?; derives.content.iter().find_map(|pm| { pm.path.is_ident("Deftly").then_some(()) })?; seen_d_d = true; Some(false) })() .unwrap_or_default() }); derive_input }) .collect() } #[derive(Default, Debug)] struct SectionState<'i> { for_: Option<(DocLoc, &'i String, &'i DirectiveTrackUsed)>, t_limits: Vec, bq_input: Option<(DocLoc, &'i String)>, } fn parse_bullet( loc: DocLoc, bullet: &str, errs: &mut Errors, ss: &mut SectionState, examples_out: &mut Vec>, ) -> Result<(), String> { let bullet = re!(r"\n +").replace_all(bullet, " "); let (input, here_for, outputs) = mc!(bullet, r#"(?m)^ \* `([^`]+)`(?: for ([^:]+))?: (.*)$"#) .ok_or_else(|| format!("syntax not as expected"))?; let for_used_buf = Default::default(); let for_ = ss.for_.or_else(|| { if here_for.is_empty() { None } else { Some((loc, &here_for, &for_used_buf)) } }); let mut all_must_match = false; let limit = match for_ { None => Limit::True, Some((loc, for_, for_used)) => { for_used.note(); // Explicit error handling because we want to use the right loc: // it might be from an earlier directive. match Limit::parse( for_, &mut all_must_match, Some(&mut ss.t_limits), ) { Ok(y) => y, Err(m) => { errs.wrong(loc, m); return Ok(()); } } } }; let mut poss = |output: Result<&_, &_>| { Ok::<_, String>({ examples_out.push(PossibilitiesExample::new( loc, &input, limit.clone(), all_must_match, output, ExpectedOutputNoneGrouping::Explicit, )?); }) }; if let Some((mut rhs,)) = mc!(outputs, r"true for (.+)$") { if for_.is_some() { return Err(format!( "in condition example, `for ...` must be on RHS" )); } let mut true_contexts = vec![]; while !rhs.is_empty() { let (for_, new_rhs) = mc!(rhs, r"([^,]+)(?:$|, and |, )(.*)") .ok_or_else(|| format!( r#"bad True context syntax for condition example: "{}""#, rhs, ))?; rhs = new_rhs; let lim = Limit::parse(&for_, &mut false, None)?; true_contexts.push((for_, lim)); } examples_out.push(Box::new(ConditionExample::new( loc, input, true_contexts, ))); } else if m!(outputs, "^nothing$") { poss(Ok(""))?; } else if let Some((msg,)) = mc!(outputs, r"error, ``(.*)``$") .or_else(|| mc!(outputs, r"rejected, ``(.*)``$")) { poss(Err(msg.trim()))?; } else { let mut outputs = outputs; while !outputs.is_empty() { let (p, rest) = [ "(?m)^`([^`]+)`(?:, (.*)|)$", "(?m)^``((?:[^`]+|`[^`])*)``(?:, (.*)|)$", ] .iter() .find_map(|re| mc!(outputs, re)) .ok_or_else(|| { format!( r#"bad (tail of) bullet point examples "{}""#, outputs, ) })?; poss(Ok(&p))?; outputs = rest; } } Ok(()) } fn examples_section<'i>( input: impl Iterator, errs: &mut Errors, out: &mut Vec>, ) { let mut input = input.peekable(); let mut ss = SectionState::default(); while let Some(ii) = input.next() { match ii { II::Bullet { loc, bullet } => { match parse_bullet(*loc, bullet, errs, &mut ss, out) { Ok(()) => {} Err(e) => errs.wrong( *loc, format_args!( "failed to parse bullet point: {:?}: {}", bullet, e, ), ), } } II::Directive { loc: d_loc, d, used, } => match d { ID::For { for_: new } => ss.for_ = Some((*d_loc, new, used)), ID::ForToplevelsConcat { toplevels } => { used.note(); if matches!(input.peek(), Some(II::Paragraph { .. })) { _ = input.next(); } let (bq_input_loc, bq_input) = match ss.bq_input.take() { Some(y) => y, None => { errs.wrong(*d_loc, "for-toplevels-concat but no previous blockquote for input"); continue; } }; match input.next() { Some(II::BlockQuote { options: _, content, .. }) => { let example = ForToplevelsConcatExample { loc: bq_input_loc, input: (*bq_input).clone(), toplevels: toplevels.clone(), output: content.clone(), }; out.push(Box::new(example)); } _ => { errs.wrong(*d_loc, "for-toplevels-concat not followed by output blockquote"); continue; } } } _ => {} }, II::BlockQuote { loc, options, content, } => { // Tests flagged *just* with `rust` are tested by // rustdoc. For our tests which we don't want to run // that way, we say `rust,dd-directly`, and wrap // the whole thing in invisible `let _ = r#".."#`. if m!(options, r"^rust$") { continue; } if let Some((prev, _)) = ss.bq_input { errs.wrong(prev, "unused blockquote, simply followed by another - missing directive?"); } ss.bq_input = Some((*loc, content)); } II::Paragraph { loc, content } => { if m!(content, r#"(?:
    \n)+"#) { // ignore the
    starts that occur // before each of our headings. continue; } errs.wrong(*loc, "unmarked text in examples section"); } II::Heading { .. } => panic!("heading in subsection!"), } } } fn blockquotes_after_directive<'o, DD>( input: &'o Preprocessed, mut is_introducer: impl FnMut(&InputDirective) -> Option
    + 'o, ) -> impl Iterator + 'o { input .iter() .enumerate() .filter_map(move |(i, ii)| match ii { II::Directive { loc: d_loc, used, d, } => { let dd = is_introducer(d)?; used.note(); Some((i, *d_loc, dd)) } _ => None, }) .map(move |(i, d_loc, dd)| match input.get(i + 1) { Some(II::BlockQuote { loc: bq_loc, content, .. }) => (d_loc, dd, *bq_loc, &**content), _ => bail(d_loc, "directive not followed by blockquote"), }) } fn process_example_sections( input: &Preprocessed, errs: &mut Errors, ) -> Vec> { let mut examples_out = vec![]; for (ia, ib) in input .iter() .enumerate() .filter_map(|(i, ii)| match ii { II::Heading { depth, text, .. } if m!(text, r"^Examples?\b") => { Some((i, depth)) } _ => None, }) .map(|(ia, depth_a)| { let ib = input .iter() .enumerate() .skip(ia + 1) .find_map(|(ib, ii)| match ii { II::Heading { depth: depth_b, .. } if depth_b <= depth_a => { Some(ib) } _ => None, }) .unwrap_or(input.len()); (ia, ib) }) { let mut input = input[ia..ib].iter(); loop { let is_heading = |ii: &InputItem| matches!(ii, II::Heading { .. }); examples_section( input.take_while_ref(|ii| !is_heading(ii)), errs, &mut examples_out, ); match input.next() { None => break, Some(ii) if is_heading(ii) => {} Some(ii) => panic!("shouldn't be {:?}", ii), } } } examples_out } fn extract_by_picture( chars: [char; N], picture_s: &str, data_s: &str, ) -> Result<[String; N], String> { let picture = picture_s.chars().collect_vec(); let data: Vec = format!("{:().trim().to_string(); Ok::<_, String>(part) })() .unwrap_or_else(|e: String| { if error.is_ok() { error = Err(e); } Default::default() }) }); (|| { error?; if let Some(wrong) = picture.iter().cloned().position(|c| { !(c.is_whitespace() || c == '#' || chars.iter().any(|tc| c == *tc)) }) { return Err(format!( r"bad character in picture, not space of '#' or one of {chars:?} picture: {picture_s} here: {nil:pad$}^", nil = "", pad = wrong, )); } for i in 0..data.len() { if data[i].is_whitespace() { continue; } if picture.get(i) == Some(&'#') { continue; } if used.get(i) == Some(&true) { continue; } return Err(format!( r"unexpected text, not in a column: picture: {picture_s} data: {data_s} here: {nil:pad$}^", nil = "", pad = i, )); } Ok::<_, String>(output) })() } fn extract_possibilites_blockquotes( input: &Preprocessed, errs: &mut Errors, examples_out: &mut Vec>, ) { for (_d_loc, (p_loc, picture), bq_loc, content) in blockquotes_after_directive(input, |d| match d { ID::PossibilitiesBlockquote { heading_picture_loc: p_loc, heading_picture, } => Some((*p_loc, heading_picture.clone())), _ => None, }) { let fields = ['i', 'f', 'o']; // Prechecking allows us to bail on the whole blockquote if // the picture is wrong, without requiring extract_by_picture // to distinguish bad pictures from bad data. match extract_by_picture(fields, &picture, "") { Err(m) => { errs.wrong(p_loc, format_args!("invalid picture line: {}", m)); return; } Ok([..]) => {} } let mut t_limits = vec![]; for (lno, l) in content.lines().enumerate() { let l_loc = bq_loc + lno + 1; match (|| { let columns = extract_by_picture(fields, &picture, l)?; for (c, data) in izip!(fields, &columns) { if data.is_empty() { return Err(format!( "missing information for column '{c}'" )); } } let [input, for_, output] = columns; let for_ = re!("^for ").replace_all(&for_, ""); let mut all_must_match = false; let limit = Limit::parse( &for_, &mut all_must_match, Some(&mut t_limits), )?; PossibilitiesExample::new( l_loc, &input, limit, all_must_match, Ok(&output), ExpectedOutputNoneGrouping::Flatten, ) })() { Ok(example) => examples_out.push(example), Err(m) => errs.wrong(l_loc, m), } } } } pub fn extract( errs: &mut Errors, ) -> (Vec, Vec>) { let iis = read_preprocess(errs); let structs = extract_structs(&iis); let mut examples = process_example_sections(&iis, errs); extract_possibilites_blockquotes(&iis, errs, &mut examples); for ii in &iis { match ii { II::Directive { used, loc, .. } if !used.is() => { errs.wrong(*loc, "unused directive - out of place"); } _ => {} } } (structs, examples) } work/tests/directly/check_examples.rs0000664000000000000000000000727514763657303015263 0ustar //! Check the examples in the reference manual //! //! Looks for bullet points and blockquotes //! in sections with title starting "Examples": //! //! ```text //! * `INPUT`: `OUTPUT` //! * `INPUT` for struct: `OUTPUT` //! * `INPUT` for structs: `OUTPUT` //! * `INPUT` for enum: `OUTPUT` //! * `INPUT` for enums: `OUTPUT` //! * `INPUT` for enum variant: `OUTPUT` //! * `INPUT` for enum variants: `OUTPUT` //! * `INPUT` for `TYPE-OR-VARIANT`: `OUTPUT` //! * `INPUT` for `FIELD` in `TYPE-OR-VARIANT`: `OUTPUT` //! * `INPUT` for fields in `TYPE-OR-VARIANT`: `OUTPUT` //! * `INPUT` for others: `OUTPUT` //! * `INPUT` ...: ``OUTPUT``, ... //! * `INPUT` ...: nothing //! * `INPUT` ...: error, ``MESSAGE`` //! * `CONDITION`: true for SOMETHING, SOMETHING, and ...` //! ``` //! //! ("others" means not any of the preceding contexts. //! Note that double backquotes are required for "error,", //! which allows individual backquotes in the messages themselves. //! The MESSAGE must then be a substring of the actual error.) //! //! In an example of a `CONDITION`, //! `SOMETHING` can be any of the syntaxes accepted in `for ...` //! (but not "others", obviously). //! All the contexts for which it returns true must be listed. //! //! In "for" clauses you can also write //! a leading `struct ` or `Enum::`, //! and a trailing `;`, `(...);`, or `{...}`. //! //! Blockquotes ` ```rust ` are tested separately via rustdoc, so ignored here. //! //! Otherwise, they should come in pairs, with, in between, //! ```text //! //! ``` //! (which shuld be followed by introductory text for the reader). //! And then the first is expanded for each TYPE; //! the results (concatenated) must match the 2nd block. //! //! Special directives //! //! * ``: //! //! Ignore until next blank line //! //! * ``: //! //! In bullet point(s), use this as if "for FOO" was written //! (ignoring any actual "for FOO") //! Applies until end of section (or next such directive) //! //! Preceding a ` ```...``` ` quote //! //! * ``: //! //! The quote has the example structs //! use super::*; mod conditions; mod contexts; mod for_toplevels_concat; mod possibilities; mod reference_extract; use conditions::ConditionExample; use contexts::{for_every_example_context, ContextExt as _, Limit}; use for_toplevels_concat::ForToplevelsConcatExample; use possibilities::PossibilitiesExample; const INPUT_FILE: &str = "doc/reference.md"; pub type DocLoc = usize; /// Something that can be checked pub trait Example { fn print_checking(&self); fn check(&self, out: &mut Errors, drivers: &[syn::DeriveInput]); } /// Allows errors to be aggregated pub struct Errors { ok: Result<(), ()>, } impl Errors { fn new() -> Self { Errors { ok: Ok(()) } } fn wrong(&mut self, loc: DocLoc, msg: impl Display) { eprintln!("{INPUT_FILE}:{loc}: {msg}"); self.ok = Err(()); } } impl Drop for Errors { fn drop(&mut self) { if !std::thread::panicking() && !self.ok.is_ok() { panic!("documentation examples check failed"); } } } fn bail(loc: DocLoc, msg: impl Display) -> ! { Errors::new().wrong(loc, msg); panic!("Errors should have panicked already!"); } #[test] fn check_examples() { macros::beta::Enabled::test_with_parsing(|| { let mut errs = Errors::new(); let (structs, examples) = reference_extract::extract(&mut errs); for example in &examples { example.print_checking(); example.check(&mut errs, &structs); } eprintln!("checked {} examples", examples.len()); }); } work/tests/directly/check_meta_coding.rs0000664000000000000000000002042314763657303015704 0ustar //! Test used meta node encoding/decoding use super::*; use macros::meta::*; use PreprocessedValue as PV; use Usage as U; //---------- ctx and pmetas and ptrees walker ---------- trait NodeCall: FnMut(&PreprocessedTree) {} impl NodeCall for F where F: FnMut(&PreprocessedTree) {} fn process_nodes_ptree(ptree: &PreprocessedTree, nc: &mut impl NodeCall) { nc(ptree); match &ptree.value { PV::List(l) => process_nodes_pvl(l, nc), PV::Unit | PV::Value { .. } => {} } } fn process_nodes_pvl(pvl: &PreprocessedValueList, nc: &mut impl NodeCall) { for ptree in &pvl.content { process_nodes_ptree(ptree, nc); } } fn process_nodes_pmetas( pmetas: &[PreprocessedValueList], nc: &mut impl NodeCall, ) -> Result<(), Void> { for pvl in pmetas { process_nodes_pvl(pvl, nc); } Ok(()) } fn process_nodes_ctx(ctx: &Context, nc: &mut impl NodeCall) { process_nodes_pmetas(&ctx.pmetas, nc).void_unwrap(); WithinVariant::for_each(&ctx, |ctx, wv| { process_nodes_pmetas(&wv.pmetas, nc)?; WithinField::for_each(ctx, |_ctx, wf| { process_nodes_pmetas(&wf.pfield.pmetas, nc) }) }) .void_unwrap(); } fn process_nodes_top( top: &syn::DeriveInput, scenario: &[(&syn::Path, Option)], start: impl FnOnce(&Context), mut nc: impl FnMut(&PreprocessedTree, Option), finish: impl FnOnce(Context) -> T, ) -> T { Context::call(&top, &dummy_path(), None, |ctx| { start(&ctx); let mut rs = scenario.iter(); process_nodes_ctx(&ctx, &mut |ptree: &PreprocessedTree| { let (node_path, node_allow) = *rs.next().unwrap(); assert_eq!(ptree.path, *node_path); nc(ptree, node_allow); }); assert!(rs.next().is_none()); Ok(finish(ctx)) }) .unwrap() } //---------- core algorithm ---------- type NodeInScenario<'n> = (&'n syn::Path, Option); struct ScenarioDebug<'a>(&'a [NodeInScenario<'a>]); fn roundtrip_scenario( top: &syn::DeriveInput, input_scenario: &[NodeInScenario], output_scenario: &[NodeInScenario], ) { let encoded = process_nodes_top( &top, input_scenario, |_ctx| {}, |ptree, allow| { if let Some(allow) = allow { ptree.update_used(allow); } }, |ctx| ctx.encode_metas_used().stream(), ); process_nodes_top( &top, output_scenario, |ctx| { Parser::parse2( |input: ParseStream| ctx.decode_update_metas_used(input), encoded.clone(), ) .unwrap(); }, |ptree, allow| { assert_eq!( ptree.used.get(), allow, "mismatch; at ptree.path={}; encoded {}; scenario I={:?} O={:?}", &encoded, ptree.path.to_token_stream(), ScenarioDebug(input_scenario), ScenarioDebug(output_scenario), ) }, |_ctx| {}, ); } impl Debug for ScenarioDebug<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "[ ")?; for (p, a) in self.0 { let a = match a { None => '-', Some(U::BoolOnly) => '?', Some(U::Value) => '=', }; write!(f, "{}{}", p.to_token_stream(), a)?; } write!(f, "]") } } //---------- test matrix generation ---------- fn check_matching_for(topkind: &str, variants: &[&str], fields: &[&str]) { let mk_ident = |s: &_| syn::Ident::new(s, Span::call_site()); let mut vs_toks = TokenStream::new(); let cell_paths: Vec = vec![]; let cell_paths = RefCell::new(cell_paths); let add_metas = |forwhat, full| { let mut metas = TokenStream::new(); let mut cell_paths = cell_paths.borrow_mut(); for bi in [0, 1] { let mut bmetas = Punctuated::<_, Token![,]>::new(); for mi in [0, 1] { // trim test matrix: // one meta node only if we haven't got n^5 already // (ideally, we'd do some kind of two-pass thing where // we prioritise things at the end, but that'd be a pain) let full = full && cell_paths.len() < 6; if !full && [bi, mi] != [0, 0] { continue; } let name = mk_ident(&format!("{}{}{}", forwhat, bi, mi)); cell_paths.push(name.clone().into()); bmetas.push(name); } if bmetas.len() != 0 { metas.extend(quote!(#[deftly(#bmetas)])); } } metas }; // trim test matrix: // multiple meta nodes for toplevel only if there's only 1 variant let tmetas = add_metas("t", variants.len() <= 1); let aliased = if topkind == "struct" { // The toplevel attributes on a struct can be seen by a template in // two ways: via tmeta, or via vmeta. We test both. // We *dnn't* filter out the synthetic struct toplevel variant, // in process_nodes_top. Which means, we will go through these // toplevel attributes twice. // (A development version of the meta used handling // had a bug relating to this aliasing of the struct toplevel.) let mut cell_paths = cell_paths.borrow_mut(); let aliased = cell_paths.len(); cell_paths.extend_from_within(..); aliased } else { 0 }; for (vindex, &vname) in variants.iter().enumerate() { let mut fs_toks = TokenStream::new(); let vinfo = if vname != "" { let vident = mk_ident(vname); // trim test matrix: // multiple meta nodes only for the last variant let vmetas = add_metas("v", vindex == variants.len() - 1); Some((vident, vmetas)) } else { None }; for &fname in fields { let fident = mk_ident(fname); let fmetas = add_metas("f", true); fs_toks.extend(quote!( #fmetas #fident: (), )); } if let Some((vident, vmetas)) = &vinfo { fs_toks = quote!( #vmetas #vident { #fs_toks }, ) } vs_toks.extend(fs_toks); } let topkind: TokenTree = syn::parse_str(topkind).unwrap(); let top_toks = quote!( #tmetas #topkind Data { #vs_toks } ); eprintln!("{}", top_toks); let top: syn::DeriveInput = syn::parse2(top_toks.clone()).unwrap(); let cell_paths = cell_paths.into_inner(); // trim test matrix: // test both values and booleans only if we're short let allows = if cell_paths.len() <= 4 { const A: &[U] = &[U::BoolOnly, U::Value]; A } else { const A: &[U] = &[U::BoolOnly]; A }; for scenario in cell_paths .iter() .map(|path| { chain!( [None], // allows.into_iter().copied().map(Some), ) .map(move |ra| (path, ra)) }) .multi_cartesian_product() { let mut output_scenario; let output_scenario = if aliased == 0 { &scenario } else { output_scenario = scenario.clone(); let (a, b) = output_scenario[0..aliased * 2].split_at_mut(aliased); for (a, b) in izip!(a, b) { let u: Option = cmp::max(a.1, b.1); a.1 = u; b.1 = u; } &output_scenario }; roundtrip_scenario(&top, &scenario, output_scenario); } } #[test] fn check_matching() { for topkind in ["struct", "enum"] { for variants in if topkind == "struct" { const VS: &[&[&str]] = &[&[""]]; VS } else { const VS: &[&[&str]] = &[&[], &["V1"], &["V1", "V2"]]; VS } { for fields in if variants.is_empty() { const FS: &[&[&str]] = &[&[]]; FS } else { const FS: &[&[&str]] = &[&[], &["f1"], &["f1", "f2"]]; FS } { check_matching_for(topkind, variants, fields); } } } } work/tests/directly/check_semver_check.rs0000664000000000000000000001072214763657303016072 0ustar //! Test that: //! //! * The latest old-b version used by `tests/compat/old-b` //! is accepted by the checking macro. //! //! * All the versions in the CHANGELOG which re textually //! before (so, later than) that version are accepted. //! //! * The textually next version in the CHANGELOG //! (which ought to be the one just before the breaking change) //! is rejected. //! //! * The current version (from our toplevel Cargo.toml) is accepted. use std::fs; use super::*; #[derive(Clone, Eq, PartialEq)] struct Version(String); impl Display for Version { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { Display::fmt(&self.0, f) } } struct TrackedVersion { v: Version, why: String, } impl TrackedVersion { fn new(v: impl Display, why: impl Display) -> Self { let v = Version(v.to_string()); let why = why.to_string(); println!("{why:25} {v}"); TrackedVersion { v, why } } } impl ToTokens for TrackedVersion { fn to_tokens(&self, out: &mut TokenStream) { let lit = proc_macro2::Literal::string(&self.v.0); out.extend([TT::Literal(lit)]); } } fn read_tested_back_compat() -> TrackedVersion { // We test (only) old-b here: that's the test case that tests // a template user using older d-d // depending on a template exporter using current d-d, // which is what the semver check (called in the exporter) // is guaranteeing will work. const PKG: &str = "tests/compat/old-b"; let cargo = fs::read_to_string(format!("../{PKG}/Cargo.toml")) .unwrap() .parse::() .unwrap(); let dep = &cargo // .as_table() .unwrap()["dependencies"] .as_table() .unwrap()["derive-deftly"]; let version = if let Some(t) = dep.as_table() { &t["version"] } else { dep }; let version = version.as_str().unwrap().strip_prefix("=").unwrap(); TrackedVersion::new(version, PKG) } fn read_current() -> TrackedVersion { let cargo = fs::read_to_string("../Cargo.toml") .unwrap() .parse::() .unwrap(); let version = cargo // .as_table() .unwrap()["package"] .as_table() .unwrap()["version"] .as_str() .unwrap(); TrackedVersion::new(version, "current") } fn read_changelog( back_compat: &Version, ) -> (Vec, TrackedVersion) { const CHANGELOG: &str = "CHANGELOG.md"; let file = File::open(format!("../{CHANGELOG}")).unwrap(); let file = BufReader::new(file); let mut lines = file .lines() .enumerate() .map(|(lno, l)| (l.unwrap(), lno + 1)); let mut good = vec![]; loop { let (l, _lno) = lines.next().unwrap(); if l.starts_with("## ") && l.contains("Changelog") { break; } } let mut had_back_compat = false; loop { let (l, lno) = lines.next().unwrap(); assert!(!l.starts_with("## ")); let Some(version) = l.strip_prefix("### ") else { continue; }; let version_str = version.split_once(' ').map(|(l, _)| l).unwrap_or(version); let tracked_version = |status: &str| { TrackedVersion::new( version_str, format_args!("{CHANGELOG}:{lno} {status}"), ) }; if had_back_compat { return (good, tracked_version("broken")); } else { good.push(tracked_version("good")); } if &version_str == &back_compat.0 { had_back_compat = true; } } } fn check_1(tv: &TrackedVersion, exp: Result<(), ()>) { let got_full = if tv.v.0 == "UNRELEASED" { Ok(quote!(UNRELEASED OK)) } else { macros::semver::template_export_semver_check_func_macro( quote! { #tv }, // ) }; let got_abbrev = got_full.as_ref().map_err(|_| ()).map(|_| ()); eprintln!("{:25} {} exp={exp:?} got={got_abbrev:?}", tv.why, tv.v); assert_eq!(got_abbrev, exp, "at={} v={} got={got_full:?}", tv.why, tv.v); } #[test] fn check() { println!("collecting:"); let current = read_current(); let back_compat = read_tested_back_compat(); let (changelog_good, changelog_bad) = read_changelog(&back_compat.v); println!("checking:"); for v in chain!([¤t, &back_compat], &changelog_good) { check_1(v, Ok(())); } for v in [&changelog_bad] { check_1(v, Err(())); } } work/tests/directly/tscompare.rs0000664000000000000000000002147614763657303014304 0ustar //! Comparison of ``TokenStream`s for test cases use super::*; #[derive(Debug)] pub struct DissimilarTokenStreams { exp: TokenStream, got: TokenStream, same: TokenStream, diff: itertools::EitherOrBoth, } #[derive(Debug, Clone, Copy)] pub enum ExpectedOutputNoneGrouping { Explicit, Flatten, } /// When displaying, call `eprintln_extended_info` too pub struct Mismatch { kind: &'static str, got: String, context_desc: String, info: Option, } /// Parsed expected output from a test case #[must_use] pub struct ExpectedOutput { /// `Err` means we expected an error containing that string exp: Result, grouping: ExpectedOutputNoneGrouping, } impl Display for ExpectedOutput { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match &self.exp { Ok(y) => Display::fmt(y, f), Err(e) => write!(f, "error: {}", e), } } } impl DissimilarTokenStreams { pub fn eprintln(&self, in_title: impl Display) { use EitherOrBoth as EOB; let in_title = in_title.to_string(); eprintln!( "----- difference report {}{}-----", in_title, if in_title.is_empty() { "" } else { " " } ); eprintln!(" expected: {}", &self.exp); eprintln!(" actual: {}", &self.got); eprintln!(" similar prefix: {}", &self.same); let side = |s, getter: fn(_) -> Option| { eprintln!( " {:16} {}", format!("{} token:", s), match getter(self.diff.clone()) { None => format!("(none)"), Some(TokenTree::Group(g)) => { format!( "{}", proc_macro2::Group::new( g.delimiter(), quote!("..."), ) ) } Some(other) => format!("{}", other), } ) }; side("expected", EOB::left); side("actual", EOB::right); } } /// Convenience version of `syn::parse_str` with more convenient error pub fn parse_str(s: &str, what: &str) -> Result where T: syn::parse::Parse, { syn::parse_str(s) .map_err(|e| format!(r#"failed to parse {}: {:?}: {}"#, what, s, e)) } impl ExpectedOutput { pub fn parse( stated: Result<&str, &str>, grouping: ExpectedOutputNoneGrouping, ) -> Result { let exp = match stated { Ok(exp) => Ok({ if let Some(inner) = (|| { let exp = exp.strip_prefix("«")?; let exp = exp.strip_suffix("»")?; Some(exp.trim()) })() { assert!(matches!( grouping, ExpectedOutputNoneGrouping::Explicit, )); let inner = parse_str(inner, "output (inner group)")?; proc_macro2::Group::new(Delimiter::None, inner) .to_token_stream() } else { parse_str(exp, "output")? } }), Err(msg) => Err(msg.to_string()), }; Ok(ExpectedOutput { exp, grouping }) } pub fn compare( &self, got: syn::Result, context_desc: &str, ) -> Result<(), Mismatch> { let mk_mismatch = |kind, info, got: &dyn Display| Mismatch { kind, info, got: got.to_string(), context_desc: context_desc.to_owned(), }; let handle_syn_error = |e: syn::Error| { mk_mismatch("syn::Error", None, &format_args!("error: {}", e)) }; match &self.exp { Ok(exp) => { let got = got.map_err(handle_syn_error)?; let got = match self.grouping { ExpectedOutputNoneGrouping::Explicit => got, ExpectedOutputNoneGrouping::Flatten => { flatten_none_groups(got) } }; check_expected_actual_similar_tokens( &exp, &got, // ) .map_err(|info| mk_mismatch("mismatch", Some(info), &got))?; //println!(" MATCHED {}", &context_desc); } Err(exp) => { let got = match got { Err(n) => Ok(n), Ok(y) => Err(y), }; let got = got .map_err(|got| { mk_mismatch("unexpected success", None, &got) })? .to_string(); if !got.contains(exp) { return Err(mk_mismatch("wrong error", None, &got)); } } } Ok(()) } } impl Mismatch { pub fn eprintln_initial(&self) { eprintln!("{}: {} [{}]", self.kind, self.got, self.context_desc) } pub fn eprintln_extended(&self) { if let Some(info) = &self.info { info.eprintln(format!("[{}]", self.context_desc)); } } } /// Tries to compare but disregarding spacing, which is unpredictable /// /// What we really want to know is /// whether the two `TokenStream`s mean the same. /// This is not so straightforward. /// Neither `TokenStream` nor `TokenTree` are `PartialEq`, /// The string representation of a `TokenStream` has unpredictable spacing: /// it can even inherit *some but not all* of the input spacing, /// and, empirically, it can depend on whether the tokens went through `syn` /// (and presumably which `syn` type(s)). /// For example, output from `derive-deftly` /// that came via an expansion that used `syn::Type` /// can have different spacing to /// a string with the same meaning, converted to `TokenStream` and back. /// /// The algorithm in this function isn't perfect but I think it will do. pub fn check_expected_actual_similar_tokens( exp: &TokenStream, got: &TokenStream, ) -> Result<(), DissimilarTokenStreams> { use EitherOrBoth as EOB; /// Having `recurse` return this ensures that on error, /// we inserted precisely one placeholder message. struct ErrorPlaceholderInserted(EitherOrBoth); fn recurse( a: &TokenStream, b: &TokenStream, same_out: &mut TokenStream, ) -> Result<(), ErrorPlaceholderInserted> { let mut input = a.clone().into_iter().zip_longest(b.clone().into_iter()); loop { if input .clone() .filter_map(|eob| eob.left()) .collect::() .to_string() == "..." { // disregard rest of this group return Ok(()); } let eob = match input.next() { Some(y) => y, None => break, }; let mut mk_err = |tokens: TokenStream| { tokens.to_tokens(same_out); Err(ErrorPlaceholderInserted(eob.clone())) }; let (a, b) = match &eob { EOB::Both(a, b) => (a, b), EOB::Left(_a) => { return mk_err(quote!(MISSING_ACTUAL_TOKEN_HERE)); } EOB::Right(_b) => { return mk_err(quote!(UNEXPECTED_ACTUAL_TOKEN_HERE)); } }; if !match (a, b) { (TT::Group(a), TT::Group(b)) => { if a.delimiter() != b.delimiter() { return mk_err(quote!( FOUND_DIFFERENT_DELIMITERS_HERE )); } let mut sub = TokenStream::new(); let r = recurse(&a.stream(), &b.stream(), &mut sub); proc_macro2::Group::new(a.delimiter(), sub) .to_tokens(same_out); let () = r?; continue; } (TT::Group(_), _) => return mk_err(quote!(LH_GROUPED_HERE)), (_, TT::Group(_)) => return mk_err(quote!(RH_GROUPED_HERE)), (a, b) => a.to_string() == b.to_string(), } { return mk_err(quote!(FOUND_DIFFERENCE_HERE)); } a.to_tokens(same_out); } Ok(()) } let mut same = TokenStream::new(); recurse(exp, got, &mut same).map_err(|ErrorPlaceholderInserted(diff)| { DissimilarTokenStreams { same, diff, exp: exp.clone(), got: got.clone(), } }) } work/tests/directly.rs0000664000000000000000000000214014763657303012272 0ustar //! Arrangements for testing derive-deftly code directly //! //! We don't want to include all the exciting test code in //! `derive-deftly-macros`'s `cargo test`. //! //! Instead, we re-import the same source files here. //! //! Currently, this makes a testing version of derive-deftly without any //! of the optional features. use macros::approx_equal::flatten_none_groups; use macros::framework::*; use macros::Concatenated; use super::*; use syn::parse_quote; #[allow(dead_code)] #[path = "../macros/macros.rs"] pub(super) mod macros; mod tscompare; use tscompare::*; // PreprocessedTree contains a Cell which shouldn't be Clone, // since that duplicates the interior mutability, which can lead to bugs. assert_not_impl_any!(macros::meta::PreprocessedTree: Clone); mod check_approx_equal; mod check_details; mod check_meta_coding; #[cfg(all( feature = "recent", // examples may not work with old compiler feature = "case", ))] mod check_examples; #[allow(clippy::non_minimal_cfg)] // rust-clippy/issues/13007 #[cfg(all( feature = "recent", // we want toml parser etc. ))] mod check_semver_check; work/tests/expand/0000775000000000000000000000000014763657303011367 5ustar work/tests/expand/clone-full.expanded.rs0000664000000000000000000000457314763657303015575 0ustar //! Example which fully and precisely derives Clone //! //! This gives a basic demonstration of how to handle an enum. use derive_deftly::{define_derive_deftly, Deftly}; #[derive_deftly(PreciseClone)] struct Unit; impl Clone for Unit { fn clone(&self) -> Self { match self { Unit {} => Unit {}, } } } #[derive_deftly(PreciseClone)] struct Tuple(F); impl Clone for Tuple where F: Clone, { fn clone(&self) -> Self { match self { Tuple { 0: f_0 } => Tuple:: { 0: f_0.clone() }, } } } #[derive_deftly(PreciseClone)] struct Struct { field: F, } impl Clone for Struct where F: Clone, { fn clone(&self) -> Self { match self { Struct { field: f_field } => { Struct:: { field: f_field.clone(), } } } } } #[derive_deftly(PreciseClone)] enum Enum { Unit, Tuple(F), Struct { field: F }, } impl Clone for Enum where F: Clone, F: Clone, { fn clone(&self) -> Self { match self { Enum::Unit {} => Enum::Unit:: {}, Enum::Tuple { 0: f_0 } => Enum::Tuple:: { 0: f_0.clone() }, Enum::Struct { field: f_field } => { Enum::Struct:: { field: f_field.clone(), } } } } } #[derive_deftly(PreciseClone)] enum AllTypes { NoData, Tuple(u16, u32), Struct { a: String, b: String }, } impl Clone for AllTypes where u16: Clone, u32: Clone, String: Clone, String: Clone, { fn clone(&self) -> Self { match self { AllTypes::NoData {} => AllTypes::NoData {}, AllTypes::Tuple { 0: f_0, 1: f_1 } => { AllTypes::Tuple { 0: f_0.clone(), 1: f_1.clone(), } } AllTypes::Struct { a: f_a, b: f_b } => { AllTypes::Struct { a: f_a.clone(), b: f_b.clone(), } } } } } fn test(value: &T) { let ours = value.clone(); drop(ours); } fn main() { test(&Unit); test(&Tuple(String::new())); test(&Struct { field: 42 }); test(&Enum::<()>::Unit); test(&Enum::Tuple(String::new())); test(&Enum::Struct { field: 66 }); } work/tests/expand/clone-full.rs0000664000000000000000000000247014763657303014000 0ustar //! Example which fully and precisely derives Clone //! //! This gives a basic demonstration of how to handle an enum. use derive_deftly::{define_derive_deftly, Deftly}; define_derive_deftly! { PreciseClone: impl<$tgens> Clone for $ttype // We don't need to $( $( ) ) (ie, twice); it automatically descends. where $( $ftype: Clone, ) $twheres { fn clone(&self) -> Self { match self { $( $vpat => $vtype { $( $fname: $fpatname.clone(), ) }, ) } } } } #[derive(Deftly)] #[derive_deftly(PreciseClone)] struct Unit; #[derive(Deftly)] #[derive_deftly(PreciseClone)] struct Tuple(F); #[derive(Deftly)] #[derive_deftly(PreciseClone)] struct Struct { field: F, } #[derive(Deftly)] #[derive_deftly(PreciseClone)] enum Enum { Unit, Tuple(F), Struct { field: F }, } #[derive(Deftly)] #[derive_deftly(PreciseClone)] enum AllTypes { NoData, Tuple(u16, u32), Struct { a: String, b: String }, } fn test(value: &T) { let ours = value.clone(); drop(ours); } fn main() { test(&Unit); test(&Tuple(String::new())); test(&Struct { field: 42 }); test(&Enum::<()>::Unit); test(&Enum::Tuple(String::new())); test(&Enum::Struct { field: 66 }); } work/tests/expand/clone.expanded.rs0000664000000000000000000000166614763657303014635 0ustar //! Very simple example for deriving Clone. use derive_deftly::{define_derive_deftly, Deftly}; use std::sync::Arc; #[derive_deftly(MyClone)] struct DecoratedError { context: String, error: Arc, } impl Clone for DecoratedError { fn clone(&self) -> Self { Self { context: self.context.clone(), error: self.error.clone(), } } } fn main() { let error = std::fs::File::open("/nonexistent").unwrap_err(); let error = DecoratedError { context: "open /nonexistent".into(), error: Arc::new(error), }; let cloned_error = error.clone(); if cloned_error.error.kind() != std::io::ErrorKind::NotFound { { #[cold] #[track_caller] #[inline(never)] const fn panic_cold_explicit() -> ! { ::core::panicking::panic_explicit() } panic_cold_explicit(); }; } } work/tests/expand/clone.rs0000664000000000000000000000246114763657303013040 0ustar //! Very simple example for deriving Clone. use derive_deftly::{define_derive_deftly, Deftly}; use std::sync::Arc; // Very simple `Clone` // // Useful because it doesn't infer Clone bounds on generic type // parameters, like std's derive of Clone does. Instead, it // unconditionally attempts to implement Clone. // // Only works on `struct { }` structs. // // (This does a small subset of what the educe crate's `Clone` does.) define_derive_deftly! { MyClone: impl<$tgens> Clone for $ttype { fn clone(&self) -> Self { Self { $( $fname: self.$fname.clone(), ) } } } } // If we were to `#[derive(Clone)]`, DecoratedError wouldn't // be Clone, because io::Error isn't, even though the Arc means we can clone. #[derive(Deftly)] #[derive_deftly(MyClone)] struct DecoratedError { context: String, error: Arc, } fn main() { let error = std::fs::File::open("/nonexistent").unwrap_err(); let error = DecoratedError { context: "open /nonexistent".into(), error: Arc::new(error), }; let cloned_error = error.clone(); // This generates much less macro output than assert_eq! if cloned_error.error.kind() != std::io::ErrorKind::NotFound { panic!(); } } work/tests/expand/conditions.expanded.rs0000664000000000000000000002453714763657303015710 0ustar //! Test cases for conditions //! //! This gives a basic demonstration of how to handle an enum. use derive_deftly::{define_derive_deftly, derive_deftly_adhoc, Deftly}; trait Trait { fn shape_top(&self) -> &'static str; fn shape_fields(&self) -> &'static str; fn has_tmeta(&self) -> bool; fn has_vmeta(&self) -> bool; fn has_tgens(&self) -> bool; } trait GetUsize { fn get_usize(&self) -> Option; } #[derive_deftly(Trait, GetUsize)] #[derive_deftly_adhoc] struct Unit; impl Trait for Unit { fn shape_top(&self) -> &'static str { "struct" } fn shape_fields(&self) -> &'static str { #[allow(unused_unsafe)] unsafe { match self { #[allow(unused_variables)] Unit {} => "unit", } } } fn has_tgens(&self) -> bool { let tgens = false; let is_empty = true; if tgens != !is_empty { { #[cold] #[track_caller] #[inline(never)] const fn panic_cold_explicit() -> ! { ::core::panicking::panic_explicit() } panic_cold_explicit(); }; } tgens } fn has_tmeta(&self) -> bool { false } fn has_vmeta(&self) -> bool { #[allow(unused_unsafe)] unsafe { match self { #[allow(unused_variables)] Unit {} => false, } } } } impl GetUsize for Unit { fn get_usize(&self) -> Option { match self { #[allow(unused_variables)] Unit {} => {} }; #[allow(unreachable_code)] None } } #[derive_deftly(Trait, GetUsize)] #[deftly(hi(ferris))] struct Tuple(usize); impl Trait for Tuple { fn shape_top(&self) -> &'static str { "struct" } fn shape_fields(&self) -> &'static str { #[allow(unused_unsafe)] unsafe { match self { #[allow(unused_variables)] Tuple { 0: f_0 } => "tuple", } } } fn has_tgens(&self) -> bool { let tgens = false; let is_empty = true; if tgens != !is_empty { { #[cold] #[track_caller] #[inline(never)] const fn panic_cold_explicit() -> ! { ::core::panicking::panic_explicit() } panic_cold_explicit(); }; } tgens } fn has_tmeta(&self) -> bool { true } fn has_vmeta(&self) -> bool { #[allow(unused_unsafe)] unsafe { match self { #[allow(unused_variables)] Tuple { 0: f_0 } => false, } } } } impl GetUsize for Tuple { fn get_usize(&self) -> Option { match self { #[allow(unused_variables)] Tuple { 0: f_0 } => { return Some(*f_0); } }; #[allow(unreachable_code)] None } } #[derive_deftly(Trait, GetUsize)] #[derive_deftly_adhoc] #[deftly(hello(there = 42))] struct Struct { field: usize, } impl Trait for Struct { fn shape_top(&self) -> &'static str { "struct" } fn shape_fields(&self) -> &'static str { #[allow(unused_unsafe)] unsafe { match self { #[allow(unused_variables)] Struct { field: f_field } => "named", } } } fn has_tgens(&self) -> bool { let tgens = false; let is_empty = true; if tgens != !is_empty { { #[cold] #[track_caller] #[inline(never)] const fn panic_cold_explicit() -> ! { ::core::panicking::panic_explicit() } panic_cold_explicit(); }; } tgens } fn has_tmeta(&self) -> bool { false } fn has_vmeta(&self) -> bool { #[allow(unused_unsafe)] unsafe { match self { #[allow(unused_variables)] Struct { field: f_field } => true, } } } } impl GetUsize for Struct { fn get_usize(&self) -> Option { match self { #[allow(unused_variables)] Struct { field: f_field } => { return Some(*f_field); } }; #[allow(unreachable_code)] None } } #[derive_deftly(Trait, GetUsize)] #[derive_deftly_adhoc] enum Enum { #[deftly(hello(there))] Unit, #[deftly(hello(there(inner)))] Tuple(usize), Named { field: u32 }, } impl Trait for Enum { fn shape_top(&self) -> &'static str { "enum" } fn shape_fields(&self) -> &'static str { #[allow(unused_unsafe)] unsafe { match self { #[allow(unused_variables)] Enum::Unit {} => "unit", #[allow(unused_variables)] Enum::Tuple { 0: f_0 } => "tuple", #[allow(unused_variables)] Enum::Named { field: f_field } => "named", } } } fn has_tgens(&self) -> bool { let tgens = false; let is_empty = true; if tgens != !is_empty { { #[cold] #[track_caller] #[inline(never)] const fn panic_cold_explicit() -> ! { ::core::panicking::panic_explicit() } panic_cold_explicit(); }; } tgens } fn has_tmeta(&self) -> bool { false } fn has_vmeta(&self) -> bool { #[allow(unused_unsafe)] unsafe { match self { #[allow(unused_variables)] Enum::Unit {} => true, #[allow(unused_variables)] Enum::Tuple { 0: f_0 } => true, #[allow(unused_variables)] Enum::Named { field: f_field } => false, } } } } impl GetUsize for Enum { fn get_usize(&self) -> Option { match self { #[allow(unused_variables)] Enum::Unit {} => {} #[allow(unused_variables)] Enum::Tuple { 0: f_0 } => { return Some(*f_0); } #[allow(unused_variables)] Enum::Named { field: f_field } => {} }; #[allow(unreachable_code)] None } } #[derive_deftly(Trait)] union Union { field: usize, } impl Trait for Union { fn shape_top(&self) -> &'static str { "union" } fn shape_fields(&self) -> &'static str { #[allow(unused_unsafe)] unsafe { match self { #[allow(unused_variables)] Union { field: f_field } => "named", } } } fn has_tgens(&self) -> bool { let tgens = false; let is_empty = true; if tgens != !is_empty { { #[cold] #[track_caller] #[inline(never)] const fn panic_cold_explicit() -> ! { ::core::panicking::panic_explicit() } panic_cold_explicit(); }; } tgens } fn has_tmeta(&self) -> bool { false } fn has_vmeta(&self) -> bool { #[allow(unused_unsafe)] unsafe { match self { #[allow(unused_variables)] Union { field: f_field } => false, } } } } #[derive_deftly(Trait)] struct Generic(T); impl Trait for Generic { fn shape_top(&self) -> &'static str { "struct" } fn shape_fields(&self) -> &'static str { #[allow(unused_unsafe)] unsafe { match self { #[allow(unused_variables)] Generic { 0: f_0 } => "tuple", } } } fn has_tgens(&self) -> bool { let tgens = true; let is_empty = false; if tgens != !is_empty { { #[cold] #[track_caller] #[inline(never)] const fn panic_cold_explicit() -> ! { ::core::panicking::panic_explicit() } panic_cold_explicit(); }; } tgens } fn has_tmeta(&self) -> bool { false } fn has_vmeta(&self) -> bool { #[allow(unused_unsafe)] unsafe { match self { #[allow(unused_variables)] Generic { 0: f_0 } => false, } } } } fn static_test_unit() {} fn static_test_struct() {} fn test(top: &str, tgens: bool, fields: &str, tmeta: bool, vmeta: bool, v: impl Trait) { if !(v.shape_top() == top && v.shape_fields() == fields && v.has_tgens() == tgens && v.has_tmeta() == tmeta && v.has_vmeta() == vmeta) { { #[cold] #[track_caller] #[inline(never)] const fn panic_cold_explicit() -> ! { ::core::panicking::panic_explicit() } panic_cold_explicit(); } } } fn test_get_usize(some_usize: Option, v: impl GetUsize) { if !(v.get_usize() == some_usize) { { #[cold] #[track_caller] #[inline(never)] const fn panic_cold_explicit() -> ! { ::core::panicking::panic_explicit() } panic_cold_explicit(); } } } fn main() { static_test_unit(); static_test_struct(); test("struct", false, "unit", false, false, Unit); test("struct", false, "tuple", true, false, Tuple(0)); test("struct", false, "named", false, true, Struct { field: 0 }); test("enum", false, "unit", false, true, Enum::Unit); test("enum", false, "tuple", false, true, Enum::Tuple(0)); test("enum", false, "named", false, false, Enum::Named { field: 0 }); test("union", false, "named", false, false, Union { field: 0 }); test("struct", true, "tuple", false, false, Generic("")); test_get_usize(None, Unit); test_get_usize(Some(0), Tuple(0)); test_get_usize(Some(0), Struct { field: 0 }); test_get_usize(None, Enum::Unit); test_get_usize(Some(0), Enum::Tuple(0)); test_get_usize(None, Enum::Named { field: 0u32 }); } work/tests/expand/conditions.rs0000664000000000000000000001272614763657303014116 0ustar //! Test cases for conditions //! //! This gives a basic demonstration of how to handle an enum. use derive_deftly::{define_derive_deftly, derive_deftly_adhoc, Deftly}; trait Trait { fn shape_top(&self) -> &'static str; fn shape_fields(&self) -> &'static str; fn has_tmeta(&self) -> bool; fn has_vmeta(&self) -> bool; fn has_tgens(&self) -> bool; } define_derive_deftly! { Trait: impl<$tgens> Trait for $ttype { fn shape_top(&self) -> &'static str { ${select1 is_struct { "struct" } is_enum { "enum" } is_union { "union" } } } fn shape_fields(&self) -> &'static str { #[allow(unused_unsafe)] unsafe { match self { $( #[allow(unused_variables)] $vpat => ${select1 v_is_unit { "unit" } v_is_tuple { "tuple" } v_is_named { "named" } }, ) } } } fn has_tgens(&self) -> bool { let tgens = ${if tgens { true } else { false }}; let is_empty = ${if is_empty($tgens) { true } else { false }}; if tgens != !is_empty { panic!(); } tgens } fn has_tmeta(&self) -> bool { ${if tmeta(hi(ferris)) { true } else { false }} } fn has_vmeta(&self) -> bool { #[allow(unused_unsafe)] unsafe { match self { $( #[allow(unused_variables)] // TODO maybe ${bool COND} for ${if ... true ... false} ? $vpat => ${if vmeta(hello(there)) { true } else { false }}, ) } } } } } trait GetUsize { fn get_usize(&self) -> Option; } define_derive_deftly! { GetUsize: impl GetUsize for $ttype { fn get_usize(&self) -> Option { match self { $( #[allow(unused_variables)] $vpat => { $( ${when approx_equal($ftype, usize)} return Some(*$fpatname); ) } ) }; #[allow(unreachable_code)] None } } } #[derive(Deftly)] #[derive_deftly(Trait, GetUsize)] #[derive_deftly_adhoc] struct Unit; #[derive(Deftly)] #[derive_deftly(Trait, GetUsize)] #[deftly(hi(ferris))] struct Tuple(usize); #[derive(Deftly)] #[derive_deftly(Trait, GetUsize)] #[derive_deftly_adhoc] // allow this, which is matched only as bool #[deftly(hello(there = 42))] struct Struct { field: usize, } #[derive(Deftly)] #[derive_deftly(Trait, GetUsize)] #[derive_deftly_adhoc] // allow hello(there(nner)) which is never matched enum Enum { #[deftly(hello(there))] Unit, #[deftly(hello(there(inner)))] Tuple(usize), Named { field: u32, }, } #[derive(Deftly)] #[derive_deftly(Trait)] union Union { field: usize, } #[derive(Deftly)] #[derive_deftly(Trait)] struct Generic(T); derive_deftly_adhoc! { Unit: fn static_test_unit() { // bad is an error ${if false { bad }} ${if true {} else { bad }} ${if not(false) {} else { bad }} ${if not(true) { bad }} ${if any() { bad }} ${if any(false) { bad }} ${if any(true) {} else { bad }} ${if any(true,false) {} else { bad }} ${if any(false,true) {} else { bad }} ${if all() {} else { bad }} ${if all(false) { bad }} ${if all(true) {} else { bad }} ${if all(true,false) { bad }} ${if all(false,true) { bad }} ${if approx_equal({}, {wobble}) { bad }} ${if approx_equal({wobble}, {}) { bad }} } } derive_deftly_adhoc! { Struct: fn static_test_struct() { $( ${if approx_equal($ftype, r#usize) { bad }} ) } } fn test( top: &str, tgens: bool, fields: &str, tmeta: bool, vmeta: bool, v: impl Trait, ) { if !(v.shape_top() == top && v.shape_fields() == fields && v.has_tgens() == tgens && v.has_tmeta() == tmeta && v.has_vmeta() == vmeta) { panic!() } } fn test_get_usize(some_usize: Option, v: impl GetUsize) { if !(v.get_usize() == some_usize) { panic!() } } fn main() { static_test_unit(); static_test_struct(); test("struct", false, "unit", false, false, Unit); test("struct", false, "tuple", true, false, Tuple(0)); test("struct", false, "named", false, true, Struct { field: 0 }); test("enum", false, "unit", false, true, Enum::Unit); test("enum", false, "tuple", false, true, Enum::Tuple(0)); test( "enum", false, "named", false, false, Enum::Named { field: 0 }, ); test("union", false, "named", false, false, Union { field: 0 }); test("struct", true, "tuple", false, false, Generic("")); test_get_usize(None, Unit); test_get_usize(Some(0), Tuple(0)); test_get_usize(Some(0), Struct { field: 0 }); test_get_usize(None, Enum::Unit); test_get_usize(Some(0), Enum::Tuple(0)); test_get_usize(None, Enum::Named { field: 0u32 }); } work/tests/expand/const-generics-recent.expanded.rs0000664000000000000000000000263314763657303017731 0ustar //! Test/example for const generics, including default values //! //! This is a separate file rather than (say) integrated into //! `partial-ord.rs` or `ref-version.rs` because this feature is new //! in Rust and our MSRV can't compile it. //! //! `recent` in the filename arranges for it not to be run by the MSRV //! compiler. #![allow(dead_code)] use derive_deftly::{derive_deftly_adhoc, Deftly}; #[derive_deftly_adhoc] struct Unit; #[derive_deftly_adhoc] struct ConstOnly; #[derive_deftly_adhoc] enum Enum { Unit, Tuple([T; N]), } enum EnumCopy { Unit, Tuple([T; N]), } impl From> for EnumCopy { fn from(orig: Enum) -> Self { match orig { Enum::Unit {} => EnumCopy::Unit:: {}, Enum::Tuple { 0: f_0 } => EnumCopy::Tuple:: { 0: f_0 }, } } } fn main() { let _: Option = None; let _ = Enum::Unit::; let _ = Enum::Tuple([42; 2]); let assert = |ok: bool| { if !ok { { #[cold] #[track_caller] #[inline(never)] const fn panic_cold_explicit() -> ! { ::core::panicking::panic_explicit() } panic_cold_explicit(); }; } }; assert(!false); assert(true); } work/tests/expand/const-generics-recent.rs0000664000000000000000000000300014763657303016127 0ustar //! Test/example for const generics, including default values //! //! This is a separate file rather than (say) integrated into //! `partial-ord.rs` or `ref-version.rs` because this feature is new //! in Rust and our MSRV can't compile it. //! //! `recent` in the filename arranges for it not to be run by the MSRV //! compiler. #![allow(dead_code)] use derive_deftly::{derive_deftly_adhoc, Deftly}; #[derive(Deftly)] #[derive_deftly_adhoc] struct Unit; #[derive(Deftly)] #[derive_deftly_adhoc] struct ConstOnly; #[derive(Deftly)] #[derive_deftly_adhoc] enum Enum { Unit, Tuple([T; N]), } derive_deftly_adhoc! { Enum: $tvis $tdefkwd ${paste $tdeftype Copy} ${tdefvariants $( ${vdefbody $vname $( $fvis ${fdefine $fname} $ftype, ) } ) } impl<$tgens> From<$ttype> for ${paste $ttype Copy} { fn from(orig: $ttype) -> Self { match orig { $( ${vpat} => ${vtype self=${paste $ttype Copy}} { $( $fname: $fpatname, ) }, ) } } } } fn main() { let _: Option = None; let _ = Enum::Unit::; let _ = Enum::Tuple([42; 2]); let assert = |ok: bool| { if !ok { panic!(); } }; assert(!derive_deftly_adhoc! { Unit: ${if tgens { true } else { false }} }); assert(derive_deftly_adhoc! { ConstOnly: ${if tgens { true } else { false }} }); } work/tests/expand/dbg-all-keywords.expanded.rs0000664000000000000000000000063214763657303016674 0ustar use derive_deftly::{define_derive_deftly, Deftly}; #[allow(dead_code)] #[derive_deftly(Dbg, DbgVariants, DbgFields, DbgNested)] enum Enum { Unit, Tuple(usize), Struct { field: String }, } #[allow(dead_code)] #[derive_deftly(Dbg)] struct Unit; #[allow(dead_code)] #[derive_deftly(Dbg)] struct Tuple(usize); #[allow(dead_code)] #[derive_deftly(Dbg)] struct Struct { field: String, } fn main() {} work/tests/expand/dbg-all-keywords.rs0000664000000000000000000000151414763657303015105 0ustar use derive_deftly::{define_derive_deftly, Deftly}; define_derive_deftly! { Dbg: $dbg_all_keywords } define_derive_deftly! { DbgVariants: ${for variants { $dbg_all_keywords }}} define_derive_deftly! { DbgFields: ${for fields { $dbg_all_keywords }}} define_derive_deftly! { DbgNested: ${define FNAME $fname} ${defcond IS_ENUM is_enum} ${for variants { ${for fields { $dbg_all_keywords }}}} } #[allow(dead_code)] #[derive(Deftly)] #[derive_deftly(Dbg, DbgVariants, DbgFields, DbgNested)] enum Enum { Unit, Tuple(usize), Struct { field: String }, } #[allow(dead_code)] #[derive(Deftly)] #[derive_deftly(Dbg)] struct Unit; #[allow(dead_code)] #[derive(Deftly)] #[derive_deftly(Dbg)] struct Tuple(usize); #[allow(dead_code)] #[derive(Deftly)] #[derive_deftly(Dbg)] struct Struct { field: String, } fn main() {} work/tests/expand/debug.expanded.rs0000664000000000000000000000456714763657303014626 0ustar //! More complex example with multiple, complex, attributes etc. //! //! Includes an example of an attribute containing Rust syntax. //! //! Read "clone.rs" and "hash.rs" first for simpler examples. use std::fmt::{self, Debug, Formatter}; use derive_deftly::{define_derive_deftly, Deftly}; use derive_deftly_tests::*; mod custom { use super::*; pub fn fmt( ds: &mut fmt::DebugStruct, name: &'static str, value: &char, ) -> fmt::Result { ds.field(name, &(*value as u32)); Ok(()) } } struct PrettyVec(Vec); #[automatically_derived] impl ::core::fmt::Debug for PrettyVec { #[inline] fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { ::core::fmt::Formatter::debug_tuple_field1_finish(f, "PrettyVec", &&self.0) } } impl From<&Vec> for PrettyVec { fn from(v: &Vec) -> Self { PrettyVec(v.clone()) } } struct Opaque; #[derive_deftly(MyDebug)] #[allow(dead_code)] struct DataType { foo: u8, #[deftly(debug(into = "PrettyVec"))] bar: Vec, #[deftly(debug(skip))] opaque: Opaque, #[deftly(debug(call = "custom::fmt"))] custom: char, } impl Debug for DataType where u8: Debug, for<'x> &'x Vec: Into>, PrettyVec: Debug, char: Debug, { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { #[allow(unused_mut)] let mut ds = f.debug_struct("DataType"); ds.field("foo", &self.foo); ds.field("bar", & as From<&Vec>>::from(&self.bar)); (custom::fmt)(&mut ds, "custom", &self.custom)?; ds.finish() } } fn main() { let dt = DataType { foo: 42, bar: ["a", "bar"].iter().map(|s| s.to_string()).collect(), opaque: Opaque, custom: 'y', }; match ( &DebugExt::to_debug(&dt), &"DataType { foo: 42, bar: PrettyVec([\"a\", \"bar\"]), custom: 121 }", ) { (left_val, right_val) => { if !(*left_val == *right_val) { let kind = ::core::panicking::AssertKind::Eq; ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::None, ); } } }; } work/tests/expand/debug.rs0000664000000000000000000000454514763657303013033 0ustar //! More complex example with multiple, complex, attributes etc. //! //! Includes an example of an attribute containing Rust syntax. //! //! Read "clone.rs" and "hash.rs" first for simpler examples. use std::fmt::{self, Debug, Formatter}; use derive_deftly::{define_derive_deftly, Deftly}; use derive_deftly_tests::*; define_derive_deftly! { MyDebug: impl Debug for $ttype where $( ${if fmeta(debug(skip)) { } else if fmeta(debug(into)) { for <'x> &'x $ftype : Into< ${fmeta(debug(into)) as ty} >, ${fmeta(debug(into)) as ty} : Debug, } else { $ftype: Debug, }} ) { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), fmt::Error> { #[allow(unused_mut)] let mut ds = f.debug_struct(stringify!($tname)); $( ${if fmeta(debug(skip)) { } else if fmeta(debug(call)) { ${fmeta(debug(call)) as expr} (&mut ds, stringify!($fname), &self.$fname)?; } else if fmeta(debug(into)) { ds.field(stringify!($fname), &< ${fmeta(debug(into)) as ty} as From<&$ftype> >::from(&self.$fname) ); } else { ds.field(stringify!($fname), &self.$fname); }} ) ds.finish() } } } mod custom { use super::*; pub fn fmt( ds: &mut fmt::DebugStruct, name: &'static str, value: &char, ) -> fmt::Result { ds.field(name, &(*value as u32)); Ok(()) } } #[derive(Debug)] struct PrettyVec(Vec); impl From<&Vec> for PrettyVec { fn from(v: &Vec) -> Self { PrettyVec(v.clone()) } } struct Opaque; #[derive(Deftly)] #[derive_deftly(MyDebug)] #[allow(dead_code)] struct DataType { foo: u8, #[deftly(debug(into = "PrettyVec"))] bar: Vec, #[deftly(debug(skip))] opaque: Opaque, #[deftly(debug(call = "custom::fmt"))] custom: char, } fn main() { let dt = DataType { foo: 42, bar: ["a", "bar"].iter().map(|s| s.to_string()).collect(), opaque: Opaque, custom: 'y', }; assert_eq!( DebugExt::to_debug(&dt), "DataType { foo: 42, bar: PrettyVec([\"a\", \"bar\"]), custom: 121 }", ); } work/tests/expand/define.expanded.rs0000664000000000000000000000324114763657303014756 0ustar //! Test `${define }` and `${defcond }` #![allow(dead_code)] use derive_deftly::{derive_deftly_adhoc, Deftly}; #[derive_deftly_adhoc] struct S { a: i32, b: i32, } const X: i32 = 0; const L: &[(i32, i32)] = &[ (901, 901), (141, 141), (902, 902), (102, 102), (902, 902), (202, 202), (903, 903), (203, 203), (343, 343), (902, 902), (202, 202), (903, 903), (203, 203), (343, 343), ]; #[derive_deftly_adhoc] enum T<'g> { /// Expected value E(i32), /// Generated values (all must match corresponding `E` G(&'g [i32]), } /// Interleaved expected and generated const T_DATA: &[T] = &[ T::G(&[900 + 1]), T::G(&[100 + 1, 100 + 1]), T::E(901), T::E(101), T::G(&[900 + 2]), T::G(&[100 + 2 + 40, 140 + 2]), T::E(902), T::E(142), T::G(&[900 + 3]), T::G(&[100 + 3, 100 + 3]), T::E(903), T::E(103), T::G(&[900 + 3]), T::G(&[100 + 3, 100 + 3]), T::E(903), T::E(103), ]; fn t_compare() { let mut e = T_DATA .iter() .filter_map(|t| match t { T::E { 0: f_0 } => Some(f_0), _ => None, }); let mut g = T_DATA .iter() .filter_map(|t| match t { T::G { 0: f_0 } => Some(f_0), _ => None, }); while let Some(e) = e.next() { let g = g.next().unwrap(); for g in *g { if e != g { std::process::abort(); } } } if e.next().is_some() { std::process::abort(); } } fn main() { for (exp, got) in L { if exp != got { std::process::abort() } } t_compare(); } work/tests/expand/define.rs0000664000000000000000000000453514763657303013176 0ustar //! Test `${define }` and `${defcond }` #![allow(dead_code)] use derive_deftly::{derive_deftly_adhoc, Deftly}; #[derive(Deftly)] #[derive_deftly_adhoc] struct S { a: i32, b: i32, } derive_deftly_adhoc! { S expect items: ${define X 0} const X: i32 = $X; const L: &[(i32, i32)] = &[ ${define X 901} ${defcond C false} (901, $X), (141, ${if C { 101 } else { 141 }}), ${define X 902} ${defcond C true} (902, $X), (102, ${if C { 102 } else { 142 }}), ${for fields { (902, $X), (202, ${if C { 202 } else { 242 }}), ${define X 903} ${defcond C false} (903, $X), (203, ${if not(C) { 203 } else { 243 }}), (343, ${if C { 303 } else { 343 }}), }} ]; } #[derive(Deftly)] #[derive_deftly_adhoc] enum T<'g> { /// Expected value E(i32), /// Generated values (all must match corresponding `E` G(&'g [i32]), } derive_deftly_adhoc! { S expect items: ${define CASE { T::G(&[ 900 + $N ]), T::G(&[ 100 + $N ${if not(C) { + 40 }}, ${if C { 100 + $N } else { 140 + $N }} ]), }} /// Interleaved expected and generated const T_DATA: &[T] = &[ ${define N 1} ${defcond C true} ${CASE} T::E(901), T::E(101), ${define N 2} ${defcond C false} ${CASE} T::E(902), T::E(142), ${for fields { ${define N 3} ${defcond C true} ${CASE} T::E(903), T::E(103), }} ]; } derive_deftly_adhoc! { T expect items: fn t_compare() { $( let mut ${snake_case $vname} = T_DATA .iter() .filter_map(|t| match t { $vpat => Some($( $fpatname )), _ => None, }); ) // zip_longest without itertools while let Some(e) = e.next() { let g = g.next().unwrap(); for g in *g { if e != g { std::process::abort(); } } } if e.next().is_some() { std::process::abort(); } } } fn main() { for (exp, got) in L { if exp != got { std::process::abort() } } t_compare(); } work/tests/expand/dollar.expanded.rs0000664000000000000000000000077014763657303015005 0ustar #![allow(dead_code)] use derive_deftly::{define_derive_deftly, derive_deftly_adhoc, Deftly}; #[derive_deftly(ReuseableTemplate)] #[derive_deftly_adhoc] ///one $dollar $template end struct OneDollar; ///one $dollar $template end struct ReusedWithOneDollar; ///one $dollar $template end struct AdhocOneDollar; #[derive_deftly_adhoc] #[derive_deftly(ReuseableTemplate)] ///two $$dollars struct TwoDollars; ///two $$dollars struct ReusedWithTwoDollars; ///two $ $dollars struct AdhocTwoDollars; fn main() {} work/tests/expand/dollar.rs0000664000000000000000000000117314763657303013214 0ustar #![allow(dead_code)] use derive_deftly::{define_derive_deftly, derive_deftly_adhoc, Deftly}; define_derive_deftly! { ReuseableTemplate: $tattrs struct ${paste ReusedWith $tname}; } #[derive(Deftly)] #[derive_deftly(ReuseableTemplate)] #[derive_deftly_adhoc] #[doc=stringify!(one $ dollar $template end)] struct OneDollar; derive_deftly_adhoc! { OneDollar: $tattrs struct AdhocOneDollar; } #[derive(Deftly)] #[derive_deftly_adhoc] #[derive_deftly(ReuseableTemplate)] #[doc=stringify!(two $$ dollars)] struct TwoDollars; derive_deftly_adhoc! { TwoDollars: $tattrs struct AdhocTwoDollars; } fn main() {} work/tests/expand/expect.expanded.rs0000664000000000000000000000050214763657303015011 0ustar //! Examples / test cases for identifier pasting. //! //! Refer to `idpaste.expanded.rs` to see what this generates. #![allow(dead_code, unused_variables)] use derive_deftly::{derive_deftly_adhoc, Deftly}; #[derive_deftly_adhoc] struct DataType { field: String, } const FIELD_NAMES: &[&str] = &["field"]; fn main() {} work/tests/expand/expect.rs0000664000000000000000000000064314763657303013230 0ustar //! Examples / test cases for identifier pasting. //! //! Refer to `idpaste.expanded.rs` to see what this generates. #![allow(dead_code, unused_variables)] use derive_deftly::{derive_deftly_adhoc, Deftly}; #[derive(Deftly)] #[derive_deftly_adhoc] struct DataType { field: String, } const FIELD_NAMES: &[&str] = derive_deftly_adhoc! { DataType expect expr: &[ $( stringify!($fname), ) ] }; fn main() {} work/tests/expand/group.expanded.rs0000664000000000000000000000307414763657303014664 0ustar //! Test various grouping behaviours, including some with None-grouping //! //! None-grouping is very hard to test for, //! especially since rustc tends to ignore it - rust-lang/rust#67062. #![allow(dead_code)] use derive_deftly::{define_derive_deftly, derive_deftly_adhoc, Deftly}; use std::fmt::Display; fn assert(ok: bool) { if !ok { { #[cold] #[track_caller] #[inline(never)] const fn panic_cold_explicit() -> ! { ::core::panicking::panic_explicit() } panic_cold_explicit(); } } } #[derive_deftly_adhoc] struct OperatorPrecedence { #[deftly(value = "2")] f1: (), #[deftly(value = "3 + 4")] f2: (), } #[derive_deftly_adhoc] enum OurOption { None, } impl OurOption { fn is_none(&self) -> bool { true } } #[derive_deftly(UseFieldsLikeOption)] #[rustfmt::skip] struct UseFieldsLikeOption { ours: OurOption, stds: Option<()>, } impl UseFieldsLikeOption { fn construct_each_field() { let none = OurOption::None; assert(OurOption::is_none(&none)); let none = Option::<()>::None; assert(Option::<()>::is_none(&none)); } } #[derive_deftly_adhoc] struct TypePrecedenceDyn { parens: (dyn Display + 'static), } fn main() { let product = (2) * (3 + 4) * 1; assert(product == 14); let product = 2 * 3 + 4 * 1; assert(product == 10); let product = 2 * (3 + 4); assert(product == 14); struct TypePrecedenceDynStaticRefs { parens: &'static (dyn Display + 'static), } } work/tests/expand/group.rs0000664000000000000000000000567714763657303013110 0ustar //! Test various grouping behaviours, including some with None-grouping //! //! None-grouping is very hard to test for, //! especially since rustc tends to ignore it - rust-lang/rust#67062. #![allow(dead_code)] use derive_deftly::{define_derive_deftly, derive_deftly_adhoc, Deftly}; use std::fmt::Display; fn assert(ok: bool) { if !ok { panic!() } } #[derive(Deftly)] #[derive_deftly_adhoc] struct OperatorPrecedence { #[deftly(value = "2")] f1: (), #[deftly(value = "3 + 4")] f2: (), } #[derive(Deftly)] #[derive_deftly_adhoc] enum OurOption { None, } impl OurOption { fn is_none(&self) -> bool { true } } define_derive_deftly! { UseFieldsLikeOption: impl $ttype { fn construct_each_field() { $( let none = $ftype::None; assert($ftype::is_none(&none)); ) } } } #[derive(Deftly)] #[derive_deftly(UseFieldsLikeOption)] #[rustfmt::skip] // TODO #74 rustfmt wants to remove the :: in `stds` struct UseFieldsLikeOption { ours: OurOption, stds: Option::<()>, // Test that inherent types, and methods, can be made from `$ftype` // even if in the struct the type is specified without colons. /* BUT TODO #74 std_no_colon: Option<()>, */ } #[derive(Deftly)] #[derive_deftly_adhoc] struct TypePrecedenceDyn { parens: (dyn Display + 'static), // Test that $ftype's None group is effective to disambiguate /* BUT we can't do that right now, because * rust-lang/rust/issues/124817 * rust-lang/rust/issues/67062 * "error: ambiguous `+` in a type" bare: dyn Display + 'static, // */ } fn main() { // Test that `... as expr` gets precedence right. let product = derive_deftly_adhoc!( OperatorPrecedence: $( ${fmeta(value) as expr} * ) 1 ); assert(product == 14); // Test that just `${Xmeta()}` gets precedence right for an expression. /* BUT it doesn't right now, because * https://github.com/rust-lang/rust/issues/124974 * https://github.com/rust-lang/rust/issues/67062 let product = derive_deftly_adhoc!( OperatorPrecedence: $( ${fmeta(value) /* future default: None-Grouped tokens */ } * ) 1 ); assert!(product == 14); */ // Test showing raw tokens *wrong* answer, due to unexpected precedence let product = derive_deftly_adhoc!( OperatorPrecedence: $( ${fmeta(value) as token_stream} * ) 1 ); assert(product == 10); // Test that there's an implicit group around an expression proc macro let product = 2 * derive_deftly_adhoc!(OperatorPrecedence: 3 + 4); assert(product == 14); // Invokes the test cases in `TypePrecedenceDyn` derive_deftly_adhoc! { TypePrecedenceDyn: struct $<$ttype StaticRefs> { $( $fname: &'static $ftype, ) } } } work/tests/expand/hash.expanded.rs0000664000000000000000000000243614763657303014454 0ustar //! Simple example, including use of an #[deftly(...)] attribute //! //! Also demonstrates use of field type trait bounds. use derive_deftly::{define_derive_deftly, Deftly}; use std::fmt::Debug; use std::hash::{Hash, Hasher}; #[derive_deftly(MyHash)] struct DataType { foo: u8, #[deftly(hash(skip))] bar: Vec, } impl Hash for DataType where u8: Hash, { fn hash(&self, state: &mut H) { self.foo.hash(state); } } #[derive_deftly(MyHash)] struct Pair where S: Debug, { first: S, second: T, } impl Hash for Pair where S: Debug, S: Hash, T: Hash, { fn hash(&self, state: &mut H) { self.first.hash(state); self.second.hash(state); } } #[derive_deftly(MyHash)] struct IntPair(usize, usize); impl Hash for IntPair where usize: Hash, usize: Hash, { fn hash(&self, state: &mut H) { self.0.hash(state); self.1.hash(state); } } fn main() { let v = DataType { foo: 23, bar: <[_]>::into_vec(::alloc::boxed::box_new(["hi".into()])), }; let mut hasher = std::collections::hash_map::DefaultHasher::new(); v.hash(&mut hasher); { ::std::io::_print(format_args!("{0:x}\n", hasher.finish())); }; } work/tests/expand/hash.rs0000664000000000000000000000217114763657303012661 0ustar //! Simple example, including use of an #[deftly(...)] attribute //! //! Also demonstrates use of field type trait bounds. use derive_deftly::{define_derive_deftly, Deftly}; use std::fmt::Debug; use std::hash::{Hash, Hasher}; define_derive_deftly! { /// Derives `Hash` MyHash: ${defcond F_HASH not(fmeta(hash(skip))) } impl<$tgens> Hash for $ttype where $twheres $( ${when F_HASH} $ftype : Hash , ) { fn hash(&self, state: &mut H) { $( ${when F_HASH} self.$fname.hash(state); ) } } } #[derive(Deftly)] #[derive_deftly(MyHash)] struct DataType { foo: u8, #[deftly(hash(skip))] bar: Vec, } #[derive(Deftly)] #[derive_deftly(MyHash)] struct Pair where S: Debug, { first: S, second: T, } #[derive(Deftly)] #[derive_deftly(MyHash)] struct IntPair(usize, usize); fn main() { let v = DataType { foo: 23, bar: vec!["hi".into()], }; let mut hasher = std::collections::hash_map::DefaultHasher::new(); v.hash(&mut hasher); println!("{:x}", hasher.finish()); } work/tests/expand/idpaste.expanded.rs0000664000000000000000000000333414763657303015160 0ustar //! Examples / test cases for identifier pasting. //! //! Refer to `idpaste.expanded.rs` to see what this generates. #![allow(dead_code, unused_variables)] use derive_deftly::{derive_deftly_adhoc, Deftly}; type FieldType = (); #[derive_deftly_adhoc] struct TypeNames { /// We use std::slice::Chunks here because that way we can test /// identifier pasting with a whole path. The macro `TypeNames` /// will generate a field with type `RChunksMut. error: std::slice::Chunks<'static, ()>, #[allow(unused_parens)] parens: (std::slice::Chunks<'static, ()>), } struct PreTypeNamesPost { error: std::slice::RChunksMut<'static, ()>, parens: (std::slice::RChunksMut<'static, ()>), } #[derive_deftly_adhoc] struct TopName { top_name_field: F, } #[allow(non_snake_case)] struct PreTopNamePost { TopNameField: F, } impl ::std::panic::RefUnwindSafe for PreTopNamePost {} #[derive_deftly_adhoc] #[deftly(t_frag = "TFrag")] struct ExpandName { #[deftly(prefix = "attr", suffix = "24")] f: FieldType, k: String, #[deftly(prefix_i = "pub", suffix = "await")] p: u32, #[deftly(prefix_i = "r#pub", suffix = "")] r: u32, } struct PreExpandNamePost { attr_f_24: FieldType, k: String, pub_p_await: u32, pub_r_: u32, } struct WomExpandNameBat; struct ViaDefineTFrag; #[derive_deftly_adhoc] enum EdgeCases { Tuple(u32), Struct { r#for: u32, unneeded: u32 }, } impl EdgeCases { fn edge_0_end() {} fn edge_0forunneeded() {} fn r#enum() {} fn binding_0_end() {} fn binding_for_end() {} fn binding_unneeded_end() {} fn body() { let r#for = (); let r#for = (); let unneeded = (); let unneeded = (); } } fn main() {} work/tests/expand/idpaste.rs0000664000000000000000000000506114763657303013370 0ustar //! Examples / test cases for identifier pasting. //! //! Refer to `idpaste.expanded.rs` to see what this generates. #![allow(dead_code, unused_variables)] use derive_deftly::{derive_deftly_adhoc, Deftly}; type FieldType = (); #[derive(Deftly)] #[derive_deftly_adhoc] struct TypeNames { /// We use std::slice::Chunks here because that way we can test /// identifier pasting with a whole path. The macro `TypeNames` /// will generate a field with type `RChunksMut. error: std::slice::Chunks<'static, ()>, // Test `${paste }` of a `(...)`-grouped ty #[allow(unused_parens)] parens: (std::slice::Chunks<'static, ()>), } derive_deftly_adhoc! { TypeNames: struct ${paste Pre $tdeftype Post} { $( $fname: ${paste R $ftype Mut}, ) } } #[derive(Deftly)] #[derive_deftly_adhoc] struct TopName { top_name_field: F, } derive_deftly_adhoc! { TopName: #[allow(non_snake_case)] struct ${paste Pre $tdeftype Post} { $( ${pascal_case $fname}: $ftype ) } impl<$tgens> ::std::panic::RefUnwindSafe for ${paste Pre $ttype Post} {} } #[derive(Deftly)] #[derive_deftly_adhoc] #[deftly(t_frag = "TFrag")] struct ExpandName { #[deftly(prefix = "attr", suffix = "24")] f: FieldType, k: String, #[deftly(prefix_i = "pub", suffix = "await")] p: u32, #[deftly(prefix_i = "r#pub", suffix = "")] r: u32, } derive_deftly_adhoc! { ExpandName: ${define VIA_DEFINE ${paste ${tmeta(t_frag) as ident}}} struct ${paste "Pre" $tdeftype "Post"} { $( ${paste ${if fmeta(prefix) { ${fmeta(prefix)} _ } } ${if fmeta(prefix_i) { ${fmeta(prefix_i) as ident} _ } } $fname ${if fmeta(suffix) { _ ${fmeta(suffix) as str} } } }: $ftype, ) } struct ${pascal_case ${paste wom_ ${shouty_snake_case $tname} ${if true { _bat } else { _noise }}}}; struct $; } #[derive(Deftly)] #[derive_deftly_adhoc] enum EdgeCases { Tuple(u32), Struct { r#for: u32, r#unneeded: u32 }, } derive_deftly_adhoc! { EdgeCases: impl $ttype { fn $>() {} fn $() {} fn $<$tdefkwd>() {} $( fn $ _end>() {} // `: `ok_0_tail`, `ok_field_tail` ) fn body() { $( ${when v_is_named} let $fname = (); let $<$fname> = (); ) } } } fn main() {} work/tests/expand/litequal.expanded.rs0000664000000000000000000000136614763657303015352 0ustar //! Examples / test cases for identifier pasting. //! //! Refer to `idpaste.expanded.rs` to see what this generates. #![allow(dead_code, unused_variables)] use derive_deftly::{derive_deftly_adhoc, Deftly}; type FieldType = (); #[derive_deftly_adhoc] struct DataType { #[deftly(lit = "42")] a: (), #[deftly(lit = "042")] b: (), #[deftly(lit = "04\x32")] c: (), #[deftly(lit = r"42")] d: (), } fn main() { let p = |s: &str| { ::std::io::_print(format_args!("{0}", s)); }; p("a"); p(" simple"); p(" raw"); p(".\n"); p("b"); p(" leftpad"); p(" hex"); p(".\n"); p("c"); p(" leftpad"); p(" hex"); p(".\n"); p("d"); p(" simple"); p(" raw"); p(".\n"); } work/tests/expand/litequal.rs0000664000000000000000000000161514763657303013560 0ustar //! Examples / test cases for identifier pasting. //! //! Refer to `idpaste.expanded.rs` to see what this generates. #![allow(dead_code, unused_variables)] use derive_deftly::{derive_deftly_adhoc, Deftly}; type FieldType = (); #[derive(Deftly)] #[derive_deftly_adhoc] struct DataType { #[deftly(lit = "42")] a: (), #[deftly(lit = "042")] b: (), #[deftly(lit = "04\x32")] c: (), #[deftly(lit = r"42")] d: (), } fn main() { let p = |s: &str| print!("{}", s); derive_deftly_adhoc! { DataType: $( p(stringify!($fname)); ${if approx_equal("42", ${fmeta(lit) as str}) { p(" simple"); }} ${if approx_equal("042", ${fmeta(lit) as str}) { p(" leftpad"); }} ${if approx_equal("04\x32", ${fmeta(lit) as str}) { p(" hex"); }} ${if approx_equal(r"42", ${fmeta(lit) as str}) { p(" raw"); }} p(".\n"); ) } } work/tests/expand/macro-rules.expanded.rs0000664000000000000000000000066214763657303015761 0ustar //! Define and use macro_rules macros in a template! use derive_deftly::{define_derive_deftly, derive_deftly_adhoc, Deftly}; #[derive_deftly(DefineAndUseMacroRulesMacro[dbg])] #[derive_deftly_adhoc] struct S(usize); impl S { fn forget(self) { std::mem::forget(self); } } impl S { fn display(&self) -> String { self.0.to_string() } } fn main() { S(1).forget(); let _: String = S(2).display(); } work/tests/expand/macro-rules.rs0000664000000000000000000000134414763657303014170 0ustar //! Define and use macro_rules macros in a template! use derive_deftly::{define_derive_deftly, derive_deftly_adhoc, Deftly}; define_derive_deftly! { DefineAndUseMacroRulesMacro: impl $ttype { fn forget(self) { macro_rules! forget { { $$v:expr } => { std::mem::forget($$v) } } forget!(self); } } } #[derive(Deftly)] #[derive_deftly(DefineAndUseMacroRulesMacro[dbg])] #[derive_deftly_adhoc] struct S(usize); derive_deftly_adhoc! { S: impl $ttype { fn display(&self) -> String { macro_rules! display { { $$v:expr } => { $$v.to_string() } } display!(self.0) } } } fn main() { S(1).forget(); let _: String = S(2).display(); } work/tests/expand/meta.expanded.rs0000664000000000000000000000072114763657303014452 0ustar //! Tests for Xmeta etc. edge cases #![allow(dead_code, unused_variables)] use derive_deftly::{derive_deftly_adhoc, Deftly}; #[derive_deftly_adhoc] struct IgnoredThings { #[deftly(prefix = "pfx")] #[deftly(multi(hi, there(wombat), path::suffix = "sfx"))] #[deftly(mixed(inner = "yes"), mixed = "no")] f: String, } fn just_pfx_prefix() {} fn multi_hi_pfx() {} fn just_suffix_sfx() {} fn mixed_no() {} fn mixed_yes() {} fn defaulted() {} fn main() {} work/tests/expand/meta.rs0000664000000000000000000000176714763657303012676 0ustar //! Tests for Xmeta etc. edge cases #![allow(dead_code, unused_variables)] use derive_deftly::{derive_deftly_adhoc, Deftly}; #[derive(Deftly)] #[derive_deftly_adhoc] struct IgnoredThings { #[deftly(prefix = "pfx")] #[deftly(multi(hi, there(wombat), path::suffix = "sfx"))] #[deftly(mixed(inner = "yes"), mixed = "no")] f: String, } derive_deftly_adhoc! { IgnoredThings beta_deftly: $( // normal fmeta parsing fn $() {} // we can pick out "hi" even though the list contains other stuff ${if fmeta(multi(hi)) { fn $() {} }} // we can pick out "path::suffix" too fn $() {} // Ignores the mixed(inner) = "yes" and finds the "no" fn $() {} // Ignores the mixed = "no" and finds the inner fn $() {} ) fn ${tmeta(absent) as ident, default { defaulted }}() {} } fn main() {} work/tests/expand/partial-ord.expanded.rs0000664000000000000000000000664514763657303015755 0ustar //! Example which derives PartialOrd, treating different enum variants //! as incomparable. //! //! This gives a demonstration on how to handle two enum //! values (from the same enum) at once - in particular, patterns //! with a different prefix. use std::cmp::Ordering::{self, *}; use derive_deftly::{define_derive_deftly, Deftly}; #[derive_deftly(VeryPartialOrd)] enum Enum where G: PartialEq, { Unit, Tuple(F), Struct { field: G }, } impl PartialOrd for Enum where F: PartialOrd, G: PartialOrd, G: PartialEq, { fn partial_cmp(&self, other: &Self) -> Option { match (self, other) { (Enum::Unit {}, Enum::Unit {}) => { let ord = Equal; Some(ord) } (Enum::Tuple { 0: self_0 }, Enum::Tuple { 0: other_0 }) => { let ord = Equal; let ord = ord.then(PartialOrd::partial_cmp(self_0, other_0)?); Some(ord) } ( Enum::Struct { field: self_field }, Enum::Struct { field: other_field }, ) => { let ord = Equal; let ord = ord.then(PartialOrd::partial_cmp(self_field, other_field)?); Some(ord) } _ => None, } } } #[automatically_derived] impl ::core::marker::StructuralPartialEq for Enum where G: PartialEq, {} #[automatically_derived] impl< F: ::core::cmp::PartialEq + PartialEq, G: ::core::cmp::PartialEq, > ::core::cmp::PartialEq for Enum where G: PartialEq, { #[inline] fn eq(&self, other: &Enum) -> bool { let __self_discr = ::core::intrinsics::discriminant_value(self); let __arg1_discr = ::core::intrinsics::discriminant_value(other); __self_discr == __arg1_discr && match (self, other) { (Enum::Tuple(__self_0), Enum::Tuple(__arg1_0)) => __self_0 == __arg1_0, (Enum::Struct { field: __self_0 }, Enum::Struct { field: __arg1_0 }) => { __self_0 == __arg1_0 } _ => true, } } } fn mk_t_struct(field: &str) -> Enum { Enum::Struct { field } } fn main() { use Enum::*; expect_none(Unit::<_, ()>, Tuple(42)); expect_none(Tuple(42), mk_t_struct("")); expect_none(Tuple::<_, ()>(Tuple::<_, ()>(42)), Tuple(Unit)); expect_some(Unit::<(), ()>, Unit, Equal); expect_some(Tuple::<_, ()>(0), Tuple(0), Equal); expect_some(Tuple::<_, ()>(1), Tuple(2), Less); expect_some(Tuple::<_, ()>(4), Tuple(3), Greater); expect_some(mk_t_struct::<()>("a"), mk_t_struct("a"), Equal); expect_some(mk_t_struct::<()>("b"), mk_t_struct("c"), Less); expect_some(mk_t_struct::<()>("e"), mk_t_struct("d"), Greater); } /// Versions of assert, basically /// /// Without too many macros cluttering the expanded output fn expect(ok: bool) { if !ok { { #[cold] #[track_caller] #[inline(never)] const fn panic_cold_explicit() -> ! { ::core::panicking::panic_explicit() } panic_cold_explicit(); }; } } fn expect_none(a: T, b: T) { expect(a.partial_cmp(&b) == None); } fn expect_some(a: T, b: T, ord: Ordering) { expect(a.partial_cmp(&b) == Some(ord)); } work/tests/expand/partial-ord.rs0000664000000000000000000000432614763657303014160 0ustar //! Example which derives PartialOrd, treating different enum variants //! as incomparable. //! //! This gives a demonstration on how to handle two enum //! values (from the same enum) at once - in particular, patterns //! with a different prefix. use std::cmp::Ordering::{self, *}; use derive_deftly::{define_derive_deftly, Deftly}; define_derive_deftly! { VeryPartialOrd: impl<$tgens> PartialOrd for $ttype where $( $ftype: PartialOrd, ) $twheres { fn partial_cmp(&self, other: &Self) -> Option { match (self, other) { $( (${vpat fprefix=self_}, ${vpat fprefix=other_}) => { let ord = Equal; $( let ord = ord.then(PartialOrd::partial_cmp( ${paste self_ $fname}, ${paste other_ $fname}, )?); ) Some(ord) // (misindented by rustfmt) }, ) _ => None, } } } } #[derive(Deftly, PartialEq)] #[derive_deftly(VeryPartialOrd)] enum Enum where G: PartialEq, { Unit, Tuple(F), Struct { field: G }, } fn mk_t_struct(field: &str) -> Enum { Enum::Struct { field } } fn main() { use Enum::*; expect_none(Unit::<_, ()>, Tuple(42)); expect_none(Tuple(42), mk_t_struct("")); expect_none(Tuple::<_, ()>(Tuple::<_, ()>(42)), Tuple(Unit)); expect_some(Unit::<(), ()>, Unit, Equal); expect_some(Tuple::<_, ()>(0), Tuple(0), Equal); expect_some(Tuple::<_, ()>(1), Tuple(2), Less); expect_some(Tuple::<_, ()>(4), Tuple(3), Greater); expect_some(mk_t_struct::<()>("a"), mk_t_struct("a"), Equal); expect_some(mk_t_struct::<()>("b"), mk_t_struct("c"), Less); expect_some(mk_t_struct::<()>("e"), mk_t_struct("d"), Greater); } /// Versions of assert, basically /// /// Without too many macros cluttering the expanded output fn expect(ok: bool) { if !ok { panic!(); } } fn expect_none(a: T, b: T) { expect(a.partial_cmp(&b) == None); } fn expect_some(a: T, b: T, ord: Ordering) { expect(a.partial_cmp(&b) == Some(ord)); } work/tests/expand/ref-version.expanded.rs0000664000000000000000000000631314763657303015766 0ustar //! Example which derives a new type containing references //! //! This demonstrates how to make a new type which mirrors the driver type, //! including both structs and enums, with units, tuples or structs. //! //! It also demonstrates how to construct a new enum type //! using `$vdefvariants`, and `$vdefbody` and `$fdefine}`, //! and handling of visibility attributes. #![allow(dead_code)] use derive_deftly::{define_derive_deftly, Deftly}; #[derive_deftly(ReferenceVersion)] struct Tuple(F); #[derive_deftly_adhoc] struct TupleReference<'reference, F = ()>(&'reference F); impl<'reference, F> From<&'reference Tuple> for TupleReference<'reference, F> { fn from(ref_to_owned: &'reference Tuple) -> Self { match ref_to_owned { Tuple { 0: f_0 } => TupleReference:: { 0: f_0 }, } } } impl<'reference, F> TupleReference<'reference, F> where F: Clone, { fn cloned(&self) -> Tuple { match self { Self { 0: f_0 } => Tuple:: { 0: (**f_0).clone() }, } } } #[derive_deftly(ReferenceVersion)] struct Struct { field: F, } #[derive_deftly_adhoc] struct StructReference<'reference, F = ()> { field: &'reference F, } impl<'reference, F> From<&'reference Struct> for StructReference<'reference, F> { fn from(ref_to_owned: &'reference Struct) -> Self { match ref_to_owned { Struct { field: f_field } => { StructReference:: { field: f_field, } } } } } impl<'reference, F> StructReference<'reference, F> where F: Clone, { fn cloned(&self) -> Struct { match self { Self { field: f_field } => { Struct:: { field: (**f_field).clone(), } } } } } #[derive_deftly(ReferenceVersion)] enum Enum { Unit, Tuple(F), Struct { field: F }, } #[derive_deftly_adhoc] enum EnumReference<'reference, F = ()> { Unit, Tuple(&'reference F), Struct { field: &'reference F }, } impl<'reference, F> From<&'reference Enum> for EnumReference<'reference, F> { fn from(ref_to_owned: &'reference Enum) -> Self { match ref_to_owned { Enum::Unit {} => EnumReference::Unit:: {}, Enum::Tuple { 0: f_0 } => { EnumReference::Tuple:: { 0: f_0, } } Enum::Struct { field: f_field } => { EnumReference::Struct:: { field: f_field, } } } } } impl<'reference, F> EnumReference<'reference, F> where F: Clone, F: Clone, { fn cloned(&self) -> Enum { match self { Self::Unit {} => Enum::Unit:: {}, Self::Tuple { 0: f_0 } => { Enum::Tuple:: { 0: (**f_0).clone(), } } Self::Struct { field: f_field } => { Enum::Struct:: { field: (**f_field).clone(), } } } } } fn main() { let _: Option = None; let _: Option> = None; } work/tests/expand/ref-version.rs0000664000000000000000000000405414763657303014177 0ustar //! Example which derives a new type containing references //! //! This demonstrates how to make a new type which mirrors the driver type, //! including both structs and enums, with units, tuples or structs. //! //! It also demonstrates how to construct a new enum type //! using `$vdefvariants`, and `$vdefbody` and `$fdefine}`, //! and handling of visibility attributes. #![allow(dead_code)] use derive_deftly::{define_derive_deftly, Deftly}; define_derive_deftly! { // The output from this `dbg` is tested via tests/stderr/stderr-lib.rs ReferenceVersion dbg: ${define REFERENCE $<$tname Reference>} ${define IMPL { impl<'reference, $tgens> }} ${define REF_TYPE { $REFERENCE<'reference, $tgnames> }} #[derive(Deftly)] #[derive_deftly_adhoc] $tvis $tdefkwd $REFERENCE<'reference, $tdefgens> ${tdefvariants $( ${vdefbody $vname $( $fvis ${fdefine $fname} &'reference $ftype, ) } ) } $IMPL From<&'reference $ttype> for $REF_TYPE { fn from(ref_to_owned: &'reference $ttype) -> Self { match ref_to_owned { $( $vpat => ${vtype self=$<$ttype Reference>} { $( $fname: $fpatname, ) }, ) } } } $IMPL $REF_TYPE where $( $ftype: Clone, ) { fn cloned(&self) -> $ttype { match self { $( ${vpat self=Self} => $vtype { $( $fname: (**$fpatname).clone(), ) }, ) } } } } // We can't do this for a Unit because it would end up with // an unused lifetime. // #[derive(Deftly)] // #[derive_deftly(ReferenceVersion)] // struct Unit; #[derive(Deftly)] #[derive_deftly(ReferenceVersion)] struct Tuple(F); #[derive(Deftly)] #[derive_deftly(ReferenceVersion)] struct Struct { field: F, } #[derive(Deftly)] #[derive_deftly(ReferenceVersion)] enum Enum { Unit, Tuple(F), Struct { field: F }, } fn main() { let _: Option = None; let _: Option> = None; } work/tests/expand/second.expanded.rs0000664000000000000000000000627414763657303015010 0ustar //! Example including attribute filtering use derive_deftly::{derive_deftly_adhoc, Deftly}; #[derive_deftly_adhoc] /// Some docs pub struct ChannelsParams { /// thing /// /// paragraph #[allow(dead_code)] padding_enable: bool, #[allow(dead_code)] padding_parameters: usize, } #[automatically_derived] impl ::core::default::Default for ChannelsParams { #[inline] fn default() -> ChannelsParams { ChannelsParams { padding_enable: ::core::default::Default::default(), padding_parameters: ::core::default::Default::default(), } } } pub struct ChannelsParamsUpdates { /// /// New value, if it has changed. pub(crate) padding_enable: Option, /// /// New value, if it has changed. pub(crate) padding_parameters: Option, } #[automatically_derived] impl ::core::fmt::Debug for ChannelsParamsUpdates { #[inline] fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { ::core::fmt::Formatter::debug_struct_field2_finish( f, "ChannelsParamsUpdates", "padding_enable", &self.padding_enable, "padding_parameters", &&self.padding_parameters, ) } } #[automatically_derived] impl ::core::default::Default for ChannelsParamsUpdates { #[inline] fn default() -> ChannelsParamsUpdates { ChannelsParamsUpdates { padding_enable: ::core::default::Default::default(), padding_parameters: ::core::default::Default::default(), } } } #[automatically_derived] impl ::core::clone::Clone for ChannelsParamsUpdates { #[inline] fn clone(&self) -> ChannelsParamsUpdates { ChannelsParamsUpdates { padding_enable: ::core::clone::Clone::clone(&self.padding_enable), padding_parameters: ::core::clone::Clone::clone(&self.padding_parameters), } } } #[automatically_derived] impl ::core::cmp::Eq for ChannelsParamsUpdates { #[inline] #[doc(hidden)] #[coverage(off)] fn assert_receiver_is_total_eq(&self) -> () { let _: ::core::cmp::AssertParamIsEq>; let _: ::core::cmp::AssertParamIsEq>; } } #[automatically_derived] impl ::core::marker::StructuralPartialEq for ChannelsParamsUpdates {} #[automatically_derived] impl ::core::cmp::PartialEq for ChannelsParamsUpdates { #[inline] fn eq(&self, other: &ChannelsParamsUpdates) -> bool { self.padding_enable == other.padding_enable && self.padding_parameters == other.padding_parameters } } #[allow(dead_code)] /// Some docs struct ChannelsParamsDupliate { /// thing /// /// paragraph #[allow(dead_code)] padding_enable: bool, #[allow(dead_code)] padding_parameters: usize, } type Wombat = ChannelsParams; type K = Wombat; fn main() { let _: K = ChannelsParams::default(); { ::std::io::_print(format_args!("field name {0:?}\n", "padding_enable")); }; { ::std::io::_print(format_args!("field name {0:?}\n", "padding_parameters")); }; let u = ChannelsParamsUpdates::default(); { ::std::io::_print(format_args!("updates = {0:?}\n", & u)); }; } work/tests/expand/second.rs0000664000000000000000000000242714763657303013215 0ustar //! Example including attribute filtering use derive_deftly::{derive_deftly_adhoc, Deftly}; #[derive(Deftly, Default)] #[derive_deftly_adhoc] /// Some docs pub struct ChannelsParams { /// thing /// /// paragraph #[allow(dead_code)] padding_enable: bool, #[allow(dead_code)] padding_parameters: usize, } derive_deftly_adhoc! { ChannelsParams: #[derive(Debug, Default, Clone, Eq, PartialEq)] pub struct ChannelsParamsUpdates { $( // ${fattrs doc[0]} ${fattrs serde} /// /// New value, if it has changed. // // ${fattrs doc[+]} pub(crate) $fname: Option<$ftype>, ) } } derive_deftly_adhoc! { ChannelsParams: #[allow(dead_code)] ${tattrs doc, serde} struct ChannelsParamsDupliate { $( ${fattrs ! serde} $fname: $ftype, ) } } derive_deftly_adhoc! { ChannelsParams: type Wombat = $ tname; } type K = Wombat; fn main() { let _: K = ChannelsParams::default(); derive_deftly_adhoc! { ChannelsParams: $( println!("field name {:?}", stringify!($fname)); ) } let u = ChannelsParamsUpdates::default(); println!("updates = {:?}", &u); } work/tests/expand/select1.expanded.rs0000664000000000000000000000360014763657303015063 0ustar //! Test case / example for `${select }` use std::fmt::Debug; use derive_deftly::{derive_deftly_adhoc, Deftly}; use derive_deftly_tests::*; #[derive_deftly_adhoc] struct Both { #[deftly(left)] a: usize, #[deftly(right)] b: usize, #[deftly(right)] c: usize, } #[automatically_derived] impl ::core::default::Default for Both { #[inline] fn default() -> Both { Both { a: ::core::default::Default::default(), b: ::core::default::Default::default(), c: ::core::default::Default::default(), } } } #[automatically_derived] impl ::core::fmt::Debug for Both { #[inline] fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { ::core::fmt::Formatter::debug_struct_field3_finish( f, "Both", "a", &self.a, "b", &self.b, "c", &&self.c, ) } } struct Left { a: usize, } #[automatically_derived] impl ::core::default::Default for Left { #[inline] fn default() -> Left { Left { a: ::core::default::Default::default(), } } } #[automatically_derived] impl ::core::fmt::Debug for Left { #[inline] fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { ::core::fmt::Formatter::debug_struct_field1_finish(f, "Left", "a", &&self.a) } } struct Right { b: usize, c: usize, } fn main() { match (&Left::default().to_debug(), &"Left { a: 0 }") { (left_val, right_val) => { if !(*left_val == *right_val) { let kind = ::core::panicking::AssertKind::Eq; ::core::panicking::assert_failed( kind, &*left_val, &*right_val, ::core::option::Option::None, ); } } } } work/tests/expand/select1.rs0000664000000000000000000000141514763657303013276 0ustar //! Test case / example for `${select }` use std::fmt::Debug; use derive_deftly::{derive_deftly_adhoc, Deftly}; use derive_deftly_tests::*; #[derive(Deftly, Default, Debug)] #[derive_deftly_adhoc] struct Both { #[deftly(left)] a: usize, #[deftly(right)] b: usize, #[deftly(right)] c: usize, } derive_deftly_adhoc! { Both: #[derive(Default, Debug)] struct Left { $( ${select1 fmeta(left) { $fname: $ftype, } fmeta(right) { } } ) } struct Right { $( ${select1 fmeta(left) { } fmeta(right) { $fname: $ftype, } } ) } } fn main() { assert_eq!(Left::default().to_debug(), "Left { a: 0 }",) } work/tests/list_names.rs0000664000000000000000000000523414763657303012620 0ustar //! Test name macros and several scopes of iteration. use derive_deftly::{define_derive_deftly, Deftly}; define_derive_deftly! { ListFields: impl $ttype { fn who_are_you() -> &'static str { stringify!( $tname ) } fn list_fields() -> Vec<&'static str> { vec! [ $( stringify!( $fname ) , ) ] } } } define_derive_deftly! { ListVariants: impl $ttype { fn list_variants() -> Vec<&'static str> { vec! [ $( stringify!( $vname ) , ) ] } fn list_qualified_fields() -> Vec<(&'static str, &'static str)> { vec! [ ${for fields { (stringify!( $vname), stringify!( $fname )) , }} ] } fn list_variant_fields() -> Vec<(&'static str, Vec<&'static str>)> { vec! [ $( (stringify!($vname), vec![ $( stringify!( $fname ), ) ] ), ) ] } } } #[derive(Deftly)] #[derive_deftly(ListFields)] struct UnitStruct; #[derive(Deftly)] #[derive_deftly(ListFields)] #[allow(dead_code)] struct SimpleStruct { small: u8, medium: u16, large: String, } #[derive(Deftly)] #[derive_deftly(ListFields)] #[allow(dead_code)] struct TupleStruct(u8, u16, String); #[derive(Deftly)] #[derive_deftly(ListFields)] #[derive_deftly(ListVariants)] #[allow(dead_code)] enum Enum { UnitVariant, StructVariant { a: u8, b: u16 }, TupleVariant(u8, u16), } #[test] fn type_names() { assert_eq!(UnitStruct::who_are_you(), "UnitStruct"); assert_eq!(SimpleStruct::who_are_you(), "SimpleStruct"); assert_eq!(TupleStruct::who_are_you(), "TupleStruct"); assert_eq!(Enum::who_are_you(), "Enum"); } #[test] fn list_fields() { assert!(UnitStruct::list_fields().is_empty()); assert_eq!( SimpleStruct::list_fields(), vec!["small", "medium", "large"] ); assert_eq!(TupleStruct::list_fields(), vec!["0", "1", "2"]); assert_eq!(Enum::list_fields(), vec!["a", "b", "0", "1"]); } #[test] fn list_variants() { assert_eq!( Enum::list_variants(), vec!["UnitVariant", "StructVariant", "TupleVariant"] ); assert_eq!( Enum::list_qualified_fields(), vec![ ("StructVariant", "a"), ("StructVariant", "b"), ("TupleVariant", "0"), ("TupleVariant", "1"), ] ); assert_eq!( Enum::list_variant_fields(), vec![ ("UnitVariant", vec![]), ("StructVariant", vec!["a", "b"]), ("TupleVariant", vec!["0", "1"]), ] ); } work/tests/macrotest.rs0000664000000000000000000000123114763657303012454 0ustar #[allow(unused_imports)] use derive_deftly_tests::*; // We don't run this unless the CI explicitly enables it. // That's done only for the "cargo-test" test, // ie our pinned nightly, with full features enabled. // // We don't do expansion match tests other than on our pinned compiler. // // We don't do these tests without all derive-deftly features enabled. // So there is n testing of what feature controls what. // That's OK, we're not going to have very many features. #[cfg(feature = "macrotest")] #[test] pub fn macrotest_expand() { for path in list_expand_test_paths_for_macrotest() { macrotest::expand_args(path, ["--all-features"]); } } work/tests/minimal-ui/0000775000000000000000000000000014763657303012151 5ustar work/tests/minimal-ui/disabled.rs0000664000000000000000000000110514763657303014263 0ustar //! Test errors from missing cargo features //! //! This is exercised only //! *with* the `ui` cargo feature but *without* `full`. use derive_deftly::{Deftly, derive_deftly_adhoc}; #[derive(Deftly)] #[deftly(e = "1")] #[deftly(i = "mod m {}")] #[derive_deftly_adhoc] struct S; derive_deftly_adhoc! { S expect items: } derive_deftly_adhoc! { S: const X: () = ${tmeta(d) as expr}; } derive_deftly_adhoc! { S: ${tmeta(i) as items}; } derive_deftly_adhoc! { S beta_deftly: } derive_deftly_adhoc! { S: ${fmeta(missing) default { 42 }} } fn main() {} work/tests/minimal-ui/disabled.stderr0000664000000000000000000000204314763657303015144 0ustar error: proc macro panicked --> minimal-ui/disabled.rs:14:1 | 14 | / derive_deftly_adhoc! { 15 | | S expect items: 16 | | } | |_^ | = help: message: output syntax checking not supported, enable `expect` feature of `derive-deftly` error: ${Xmeta as expr} used but cargo feature meta-as-expr disabled --> minimal-ui/disabled.rs:19:33 | 19 | const X: () = ${tmeta(d) as expr}; | ^^^^ error: ${Xmeta as items} used but cargo feature meta-as-items disabled --> minimal-ui/disabled.rs:23:19 | 23 | ${tmeta(i) as items}; | ^^^^^ error: derive-deftly's `beta_deftly` template option is only available when the `beta` cargo feature is also enabled --> minimal-ui/disabled.rs:26:7 | 26 | S beta_deftly: | ^^^^^^^^^^^ error: beta derive-deftly feature used, which requires both the `beta` cargo feature and the `beta_deftly` template option --> minimal-ui/disabled.rs:30:22 | 30 | ${fmeta(missing) default { 42 }} | ^^^^^^^ work/tests/modules.rs0000664000000000000000000001213714763657303012132 0ustar //! Test/demonstrate within-crate cross-module scoping //! //! In practice, one would probably put the "ought to be in scope" //! names into an internal prelude module, and `use internal_prelude::*`. //! That would allow natural use of the identifiers from the prelude //! everywhere and avoid having to write qualified paths in templates //! or worry about precisely which things should be imported where. //! //! Sadly `macro_rules!` macro scoping doesn't work that way, so one //! would still need the `#[macro_use]` annotations. And, the ordering //! of the code is significant - so it can be necessary to have a particular //! ordering of `mod` statements. // Reminder, this is a *module* inside a test crate. // The name of this module is `crate::modules` // and it is within `#[cfg(test)]`. #![allow(dead_code)] struct EnumMarker; trait IsEnum { fn is_enum() -> Option; } trait NumFields { fn num_fields() -> usize; } /// When defining a template that is to be used outside its defining /// module, but inside the same crate, `#[macro_use]` is needed, on the /// containing module(s). The template is then available *textually after* /// its definition. /// /// (Exporting of a template to other crates is demonstrated in /// `tests/pub-export/pub-b/pub-a.rs` and `pub-b.rs`.) /// /// The remaining principles about use of names in a derive-deftly template /// are basically the same as those for a `macro_rules!` macro. #[macro_use] mod has_template { use derive_deftly::define_derive_deftly; /// Demonstrates use of a local name in a template. /// We must make it visible everywhere the template will be expanded. pub(super) const ENUM_MARKER: Option = Some(super::EnumMarker); define_derive_deftly! { IsEnum: // When the template wants to refer to things, it is usually most // convenient to expect the template user to bring the needed parts // into scope at the template invocation site. We do that here // for IsEnum and EnumMarker; impl<$tgens> IsEnum for $ttype { fn is_enum() -> Option { // Alternatively, the template can refer to items by // absolute crate path. The items must still be visible // at the template invocation site. // // We do that here with `ENUM_MARKER`. // // (`super::` isn't adviseable in a macro, including in a // derive-deftly template, because its meaning at the // expansion site would depend on the scope context there // (eg, module depth.) ${if is_enum { crate::modules::has_template::ENUM_MARKER } else { None } } } } } } /// When definining a driver that is to be used as a derive input /// outside its defining module, but inside the same crate, /// `#[macro_use]` is needed, on the containing module(s). The driver /// is then available for `derive_deftly_adhoc!` *textually after* its /// definition. #[macro_use] pub mod has_driver { use derive_deftly::Deftly; // Additionally, for a driver to be useable outside its module, the // driver type name and types of its fields etc., need to be visible, // for the use of expansions in other modules. pub struct Field(T); // When applying a template that has expectations about the // invocation scope, we need to satisfy those, by bringing into // scope the things that the template expects. use super::{EnumMarker, IsEnum}; #[derive(Deftly)] #[derive_deftly(IsEnum)] #[derive_deftly_adhoc] pub enum Driver { Variant(Field), } } mod adhoc_template { use derive_deftly::derive_deftly_adhoc; // When applying a template to a driver struct defined in another // module, the driver's own type name, and the names used for field // type=s etc. that the macro might muse, must be brought into scope // manually. use super::has_driver::{Driver, Field}; derive_deftly_adhoc! { // When expanding a template for a crate-local struct, pass // just the name of the struct to `derive_deftly_adhoc!`. // // (Exporting of the driver so that other crates can derive from it // is supported via `#[derive_deftly(pub)]` but it brings // namespacing awkwardness and should be used only with crate.) // // (Passing a path doesn't work for a struct with an unexported // derive-deftly driver, because the name here is turned into // the name of a `macro_rules!` macro, and a crate-local one // of those those doesn't have any path scope.) Driver: impl<$tgens> super::NumFields for $ttype { fn num_fields() -> usize { $( let _: $ftype; ) 0 + ${for fields { 1 }} } } } } #[test] fn invoke() { assert!(has_driver::Driver::<()>::is_enum().is_some()); assert_eq!(has_driver::Driver::<()>::num_fields(), 1); } work/tests/pub-export/0000775000000000000000000000000014763657303012215 5ustar work/tests/pub-export/bizarre-facade/0000775000000000000000000000000014763657303015054 5ustar work/tests/pub-export/bizarre-facade/Cargo.toml0000664000000000000000000000226314763657303017007 0ustar # WARNING - AUTOMATICALLY GENERATED # This file is generated by maint/update-bizarre from Cargo.toml # See tests/pub-export/pub-b/pub-b.rs for the general explanation # of the bizarre-* crates. [package] name = "bizarre-derive-deftly" version = "0.0.666" edition = "2021" license="MIT" authors=["Ian Jackson ", "and the contributors to Rust derive-deftly"] description="Bizarrely incompatible copy of derive-deftly, for testing" publish = false rust-version = "1.56" autotests = false [features] default = ["full"] full = ["full-msrv-1.56"] "full-msrv-1.56" = [ "case", "expect", "meta-as-expr", "meta-as-items", "minimal-1", ] minimal-1 = [] case = ["derive-deftly-macros/case", "heck"] expect = ["derive-deftly-macros/expect"] meta-as-expr = ["derive-deftly-macros/meta-as-expr"] meta-as-items = ["derive-deftly-macros/meta-as-items"] beta = ["derive-deftly-macros/beta"] [dependencies] derive-deftly-macros = { package = "bizarre-derive-deftly-macros", path = "../bizarre-macros", version = "0.0.666" } heck = { version = ">=0.4, <0.6", optional = true } [lib] path = "../../../src/lib.rs" doc = false doctest = false work/tests/pub-export/bizarre-macros/0000775000000000000000000000000014763657303015135 5ustar work/tests/pub-export/bizarre-macros/Cargo.toml0000664000000000000000000000217414763657303017071 0ustar # WARNING - AUTOMATICALLY GENERATED # This file is generated by maint/update-bizarre from macros/Cargo.toml # See tests/pub-export/pub-b/pub-b.rs for the general explanation # of the bizarre-* crates. [package] name = "bizarre-derive-deftly-macros" version = "0.0.666" edition = "2021" authors=["Ian Jackson ", "and the contributors to Rust derive-deftly"] license = "MIT" description="Bizarrely incompatible copy of derive-deftly-macros, for testing" publish = false rust-version = "1.56" [features] bizarre = [] default = ["bizarre"] case = ["heck"] expect = ["sha3", "syn/full"] meta-as-expr = ["syn/full"] meta-as-items = ["syn/full"] beta = [] [dependencies] indexmap = ">=1.8, <3" itertools = ">=0.10.1, <0.16" proc-macro-crate = ">=1.1.3, <4" proc-macro2 = "1.0.53" quote = "1" sha3 = { version = "0.10", optional = true } strum = { version = ">=0.24, <0.28", features = ["derive"] } syn = { version = "2.0.53", features = ["extra-traits"] } void = "1" heck = { version = ">=0.4, <0.6", optional = true } [lib] path = "../../../macros/macros.rs" doc = false doctest = false proc-macro = true work/tests/pub-export/bizarre-macros/build.rs0000777000000000000000000000000014763657303022367 2../../../macros/build.rsustar work/tests/pub-export/pub-a/0000775000000000000000000000000014763657303013221 5ustar work/tests/pub-export/pub-a/Cargo.toml0000664000000000000000000000052314763657303015151 0ustar [package] name = "pub-a" version = "0.0.1" edition = "2021" license="MIT" authors=["Ian Jackson ", "and the contributors to Rust derive-deftly"] description="pub testing crates - part A" publish = false [dependencies] derive-deftly = { path = "../../..", version = "*" } [lib] path = "pub-a.rs" work/tests/pub-export/pub-a/pub-a.rs0000664000000000000000000001033114763657303014571 0ustar //! Test / demo of cross-crate and compatibility //! //! Our test has two crates. This is the lower-layer one, which //! provides a trait derivable with derive_deftly (ie, a trait //! and corresponding reuseable template). //! //! It also provides a struct exported as a derive-deftly driver. #![allow(dead_code)] // Any crate exposing a derive_deftly template must re-export derive_deftly // itself at the top level. This will be used during template invocation // to find the right version of the template expansion machinery. pub use derive_deftly; pub use a_driver::ADriver; // For local use of the template, within the same crate, // `#[macro_use]` is needed to extend the textual scope of the macro. // This is because, as the compiler says, // macro-expanded `macro_export` macros from the current crate // cannot be referred to by absolute paths #[macro_use] pub mod a_trait { use derive_deftly::define_derive_deftly; /// Demonstrates use of a local name in a template. /// We must make it visible everywhere the template will be expanded. pub trait IsEnum { fn is_enum() -> Option<()>; } define_derive_deftly! { export IsEnum: // When the template wants to refer to things in its own // crate, it must use fully qualified names, starting with // `$crate`, which works the similarly in `macro_rules!`. impl<$tgens> $crate::a_trait::IsEnum for $ttype { // For a name like `Option` it is a matter of taste // whether to refer explicitly to `std`, or just rely on // the caller not having messed up their namespace. // (Again, this is similar to macro_rules!) fn is_enum() -> Option<()> { ${if is_enum { Some(()) } else { None } } } } } } mod local_use { use derive_deftly::Deftly; // When we refer to a local template, we do so without a path, // even though it was an exported template. #[derive(Deftly)] #[derive_deftly(IsEnum)] #[derive_deftly_adhoc] pub(crate) struct Local; } pub mod a_driver { use derive_deftly::Deftly; /// A struct which is exported as a derive-deftly driver (iw with /// `#[derive_deftly(pub)]` must be *visilbe* outside the crate. /// /// Doing this effectively turns the body of the struct into a macro, /// with very limited support for namespacing and hygiene! /// (So this feature should be used with care and caution.) /// /// The *user* of such an exported driver will need not only access to /// the driver macro (which is exported at the top-level of the driver's /// crate), but also all of the types involved in the struct. /// /// That includes the struct type itself, of course, but also all the /// names it refers to (for example, the types of the fields). /// The most reasonable way to do this is probably to use unqualified /// names in the driver definition, and export all of those names. /// The user can then `use *` to obtain a suitable namespace for /// invoking the driver with their own template. /// /// Additionally, of course, this exposes all of the field names and /// types of the struct. So it is probably not a good idea with a /// driver that isn't completely pub and exhaustive. (This could be /// checked by derive_deftly_adhoc!, but then we would want a way to override /// the error, too. All this additional complexity seems unwarranted /// for a feature which is so difficult to use for other reasons too.) /// /// So we will say that as a rule of thumb, we expect that any exported /// driver will have bespoke rules about how a depending crate might /// use it, and what would and wouldn't count as a semver major change. /// Anyone who *uses* an exported driver should refer to those docs, /// on pain of risking breakages due to `cargo update`. #[derive(Deftly)] #[derive_deftly_adhoc(export)] pub struct ADriver { pub field: AField, } pub struct AField(T); } #[test] fn invoke() { use a_trait::IsEnum; assert!(local_use::Local::is_enum().is_none()); } work/tests/pub-export/pub-b/0000775000000000000000000000000014763657303013222 5ustar work/tests/pub-export/pub-b/Cargo.toml0000664000000000000000000000071514763657303015155 0ustar [package] name = "pub-b" version = "0.0.1" edition = "2021" license="MIT" authors=["Ian Jackson ", "and the contributors to Rust derive-deftly"] description="pub testing crates - part B, uses bizarre version" publish = false [dependencies] pub-a = { path = "../pub-a", version = "0.0.1" } derive-deftly = { package = "bizarre-derive-deftly", path = "../bizarre-facade", version = "0.0.666" } [lib] path = "pub-b.rs" work/tests/pub-export/pub-b/build.rs0000777000000000000000000000000014763657303020332 2../../../tests/build.rsustar work/tests/pub-export/pub-b/pub-b.rs0000664000000000000000000000715314763657303014603 0ustar //! Test / demo of cross-crate and compatibility //! //! Our test has two crates. This is the higher-layer one, which //! invokes a reuseable template from pub-a, and invokes an ad-hoc //! template of its own on a driver from pub-a. //! //! This uses the bizarre-* testing versions: //! //! The bizarre-* crates are versions of the derive-deftly (library/facade) //! and derive-deftly-macros (proc macro) crates, specially mutated for //! testing. The only functional difference is that the "bizarre" versions //! require every expansion keyword to end with "_bizarre". So, for example, //! `$ttype` becomes `$ttype_bizarre`. //! //! This allows us to simultaneously test a situation where we have two //! incompatible versions of the template language: it proves that each //! crate can write in the syntax for *its* version of derive-deftly, //! and even export a template to another crate, which is using a *different* //! version. That is what we do here. //! //! The two bizarre-* crates use the very same source code. //! There is a single cargo feature `bizarre` which is enabled only //! in bizarre-derive-deftly-macros (and normally not even available), //! which controls the behaviour. //! //! The Cargo.toml files for the bizarre-* crates are maintained by //! `maint/update-bizarre`. #[cfg(not(feature = "skip_imported_driver"))] pub mod adhoc_template { use derive_deftly::derive_deftly_adhoc; pub trait NumFields { fn num_fields() -> usize; } /// When applying a template to a driver struct defined in another /// module, the driver's own type name, and the names used for field /// types etc., will; be *textually* substituted. /// /// See the note next to `pub mod a_driver` in `pub-b.rs`. /// /// Here we import the whole of the driver's module. so that the /// unqualified struct name and field names are useable here. use pub_a::a_driver::*; derive_deftly_adhoc! { // When expanding a template for a foreign struct, pass // the crate name and struct name to `derive_deftly_adhoc!`. // // (Macro scoping rules mean that the driver is visible // only at the top-level, not in the module it was defined in.) // // The top-level struct name must *also* be in scope, here, // as an unqualified name: that's what `$ttype` expands to. pub_a::ADriver: impl<$tgens_bizarre> NumFields for $ttype_bizarre { #[allow(clippy::identity_op)] fn num_fields() -> usize { $( let _: $ftype_bizarre; ) 0 + ${for_bizarre fields { 1 }} } } } } pub mod b_driver { use derive_deftly::Deftly; pub struct BField(T); /// Here we invoke a template from another crate. /// We can freely refer to local names in our struct definition, /// because the template's expansion appears here, in this scope. /// /// The template, in `pub-a.rs`, is referenced by its crate, /// but *not* its module (`pub_a::a_trait`). #[derive(Deftly)] #[derive_deftly(pub_a::IsEnum)] pub enum BDriver { Variant(BField), } use pub_a::derive_deftly_template_IsEnum as derive_deftly_template_ImportedIsEnum; #[derive(Deftly)] #[derive_deftly(ImportedIsEnum)] pub struct BDriver2; } #[test] fn invoke_template() { use pub_a::a_trait::IsEnum; assert!(b_driver::BDriver::<()>::is_enum().is_some()); } #[cfg(not(feature = "skip_imported_driver"))] #[test] fn invoke_exported_driver() { use adhoc_template::NumFields; assert_eq!(pub_a::a_driver::ADriver::<()>::num_fields(), 1); } work/tests/stderr/0000775000000000000000000000000014763657303011413 5ustar work/tests/stderr/Cargo.toml0000664000000000000000000000174014763657303013345 0ustar # See `tests/stderr.rs` [package] name = "derive-deftly-stderr-test" version = "0.0.1" edition = "2021" license="MIT" authors=["Ian Jackson ", "and the contributors to Rust derive-deftly"] description="stderr testing crates - does not build" publish = false [dependencies] derive-deftly = { path = "../..", version = "*", features = ["full"] } [features] # The purpose of this crate is to fail the build, with error messages # that we collect. But to avoid having multiple Cargo.lock, etc., # we want it to be a member of the workspace. # # So we arrange that it is entirely empty unless enabled, meaning # `disable` is disabled, *and* `enable-COLLECTION` is enabled. # (Only one COLLECTION is enabled at a time.) # See the #![cfg]s in stderr-lib.rs. # # The stderr from each COLLECTION is in COLLECTION.real-stderr. default = ["disable"] enable-main = [] enable-recent = [] disable = [] [lib] path = "stderr-lib.rs" doc = false doctest = false work/tests/stderr/main.real-stderr0000664000000000000000000001207414763657303014511 0ustar ---------- derive-deftly expansion of BadOptionsTemplate for BadOptionsDriver (start) ---------- broken template; ---------- derive-deftly expansion of BadOptionsTemplate for BadOptionsDriver (end) ---------- ---------- derive-deftly expansion, for BadOptionsDriver (start) ---------- syntax error; ---------- derive-deftly expansion, for BadOptionsDriver (end) ---------- ---------- derive-deftly dbg dump for DataType from BasicDbgs expansion (start) ---------- fn struct_fn() {} ---------- derive-deftly dbg dump for DataType from BasicDbgs expansion (end) ---------- ---------- derive-deftly dbg dump "noted" for DataType from BasicDbgs expansion (start) ---------- fn struct_noted() {} ---------- derive-deftly dbg dump "noted" for DataType from BasicDbgs expansion (end) ---------- derive-deftly dbg condition for DataType from BasicDbgs evaluated to true derive-deftly dbg condition "noted" for DataType from BasicDbgs evaluated to false ---------- derive-deftly dbg dump for DataType from BasicDbgs expansion (start) ---------- /* ERROR: must be within a field (so, in a repeat group) */ ---------- derive-deftly dbg dump for DataType from BasicDbgs expansion (end) ---------- ---------- derive-deftly dbg dump "error" for DataType from BasicDbgs expansion (start) ---------- /* ERROR: must be within a field (so, in a repeat group) */ ---------- derive-deftly dbg dump "error" for DataType from BasicDbgs expansion (end) ---------- derive-deftly dbg condition for DataType from BasicDbgs evaluated to error: must be within a field (so, in a repeat group) derive-deftly dbg condition "error" for DataType from BasicDbgs evaluated to error: must be within a field (so, in a repeat group) ---------- derive-deftly dbg dump for Tuple from Contexts expansion (start) ---------- /* ERROR: expansion only valid in enums */ ---------- derive-deftly dbg dump for Tuple from Contexts expansion (end) ---------- ---------- derive-deftly dbg dump "noted vtype" for Tuple from Contexts expansion (start) ---------- Tuple ---------- derive-deftly dbg dump "noted vtype" for Tuple from Contexts expansion (end) ---------- ---------- derive-deftly dbg dump "noted fname" for Tuple.0 from Contexts expansion (start) ---------- 0 ---------- derive-deftly dbg dump "noted fname" for Tuple.0 from Contexts expansion (end) ---------- ---------- derive-deftly dbg dump for Struct from Contexts expansion (start) ---------- /* ERROR: expansion only valid in enums */ ---------- derive-deftly dbg dump for Struct from Contexts expansion (end) ---------- ---------- derive-deftly dbg dump "noted vtype" for Struct from Contexts expansion (start) ---------- Struct ---------- derive-deftly dbg dump "noted vtype" for Struct from Contexts expansion (end) ---------- ---------- derive-deftly dbg dump "noted fname" for Struct.f from Contexts expansion (start) ---------- f ---------- derive-deftly dbg dump "noted fname" for Struct.f from Contexts expansion (end) ---------- ---------- derive-deftly dbg dump for Enum::Tuple from Contexts expansion (start) ---------- Tuple ---------- derive-deftly dbg dump for Enum::Tuple from Contexts expansion (end) ---------- ---------- derive-deftly dbg dump for Enum::Struct from Contexts expansion (start) ---------- Struct ---------- derive-deftly dbg dump for Enum::Struct from Contexts expansion (end) ---------- ---------- derive-deftly dbg dump "noted vtype" for Enum::Tuple from Contexts expansion (start) ---------- Enum :: Tuple ---------- derive-deftly dbg dump "noted vtype" for Enum::Tuple from Contexts expansion (end) ---------- ---------- derive-deftly dbg dump "noted vtype" for Enum::Struct from Contexts expansion (start) ---------- Enum :: Struct ---------- derive-deftly dbg dump "noted vtype" for Enum::Struct from Contexts expansion (end) ---------- ---------- derive-deftly dbg dump "noted fname" for Enum::Tuple.0 from Contexts expansion (start) ---------- 0 ---------- derive-deftly dbg dump "noted fname" for Enum::Tuple.0 from Contexts expansion (end) ---------- ---------- derive-deftly dbg dump "noted fname" for Enum::Struct.f from Contexts expansion (start) ---------- f ---------- derive-deftly dbg dump "noted fname" for Enum::Struct.f from Contexts expansion (end) ---------- ---------- derive-deftly dbg dump "paste" for WithGenerics from Pastes expansion (start) ---------- "pasting_" ""+"WithGenerics"+":: < T >" "_pasted" ---------- derive-deftly dbg dump "paste" for WithGenerics from Pastes expansion (end) ---------- ---------- derive-deftly dbg dump "paste error" for WithGenerics from Pastes expansion (start) ---------- ERROR: must be within a field (so, in a repeat group) "x" "y" ---------- derive-deftly dbg dump "paste error" for WithGenerics from Pastes expansion (end) ---------- ---------- derive-deftly dbg dump for DataType expansion (start) ---------- fn struct_adhoc() {} ---------- derive-deftly dbg dump for DataType expansion (end) ---------- ---------- derive-deftly dbg dump "adhoc" for DataType expansion (start) ---------- fn tdefkwed_adhoc2() {} ---------- derive-deftly dbg dump "adhoc" for DataType expansion (end) ---------- derive-deftly dbg condition "adhoc" for DataType evaluated to true work/tests/stderr/recent.real-stderr0000664000000000000000000005246214763657303015052 0ustar ---------- derive-deftly expansions dump for Enum from Dbg (start) ---------- top-level: $tname => Enum $ttype => Enum $tvis => $tgens => $tgnames => $twheres => $tdeftype => Enum $tdefgens => $tdefkwd => enum ${tdefvariants VARIANTS ..} => { VARIANTS .. } is_struct = false is_enum = true is_union = false tvis = false tgens = false $tattrs => #[allow(dead_code)] variant Unit: $vname => Unit $vtype => Enum :: Unit $vpat => Enum :: Unit {} ${vdefbody VNAME FIELDS ..} => VNAME FIELDS .. , v_is_unit = true v_is_tuple = false v_is_named = false $vattrs => variant Tuple: $vname => Tuple $vtype => Enum :: Tuple $vpat => Enum :: Tuple { 0 : f_0, } ${vdefbody VNAME FIELDS ..} => VNAME(FIELDS ..), v_is_unit = false v_is_tuple = true v_is_named = false $vattrs => variant Tuple, field 0: $fname => 0 $ftype => usize $fvis => $fdefvis => $fpatname => f_0 $fdefine => fvis = false fdefvis = false $fattrs => variant Struct: $vname => Struct $vtype => Enum :: Struct $vpat => Enum :: Struct { field : f_field, } ${vdefbody VNAME FIELDS ..} => VNAME { FIELDS .. }, v_is_unit = false v_is_tuple = false v_is_named = true $vattrs => variant Struct, field field: $fname => field $ftype => String $fvis => $fdefvis => $fpatname => f_field $fdefine => field : fvis = false fdefvis = false $fattrs => ---------- derive-deftly expansions dump for Enum from Dbg (end) ---------- ---------- derive-deftly expansions dump for Enum::Unit from DbgVariants (start) ---------- top-level: $tname => Enum $ttype => Enum $tvis => $tgens => $tgnames => $twheres => $tdeftype => Enum $tdefgens => $tdefkwd => enum ${tdefvariants VARIANTS ..} => { VARIANTS .. } is_struct = false is_enum = true is_union = false tvis = false tgens = false $tattrs => #[allow(dead_code)] variant Unit: $vname => Unit $vtype => Enum :: Unit $vpat => Enum :: Unit {} ${vdefbody VNAME FIELDS ..} => VNAME FIELDS .. , v_is_unit = true v_is_tuple = false v_is_named = false $vattrs => variant Unit, no field: $fname => $ftype => $fvis => $fdefvis => $fpatname => $fdefine => fvis = fdefvis = $fattrs => ---------- derive-deftly expansions dump for Enum::Unit from DbgVariants (end) ---------- ---------- derive-deftly expansions dump for Enum::Tuple from DbgVariants (start) ---------- top-level: $tname => Enum $ttype => Enum $tvis => $tgens => $tgnames => $twheres => $tdeftype => Enum $tdefgens => $tdefkwd => enum ${tdefvariants VARIANTS ..} => { VARIANTS .. } is_struct = false is_enum = true is_union = false tvis = false tgens = false $tattrs => #[allow(dead_code)] variant Tuple: $vname => Tuple $vtype => Enum :: Tuple $vpat => Enum :: Tuple { 0 : f_0, } ${vdefbody VNAME FIELDS ..} => VNAME(FIELDS ..), v_is_unit = false v_is_tuple = true v_is_named = false $vattrs => variant Tuple, no field: $fname => $ftype => $fvis => $fdefvis => $fpatname => $fdefine => fvis = fdefvis = $fattrs => ---------- derive-deftly expansions dump for Enum::Tuple from DbgVariants (end) ---------- ---------- derive-deftly expansions dump for Enum::Struct from DbgVariants (start) ---------- top-level: $tname => Enum $ttype => Enum $tvis => $tgens => $tgnames => $twheres => $tdeftype => Enum $tdefgens => $tdefkwd => enum ${tdefvariants VARIANTS ..} => { VARIANTS .. } is_struct = false is_enum = true is_union = false tvis = false tgens = false $tattrs => #[allow(dead_code)] variant Struct: $vname => Struct $vtype => Enum :: Struct $vpat => Enum :: Struct { field : f_field, } ${vdefbody VNAME FIELDS ..} => VNAME { FIELDS .. }, v_is_unit = false v_is_tuple = false v_is_named = true $vattrs => variant Struct, no field: $fname => $ftype => $fvis => $fdefvis => $fpatname => $fdefine => fvis = fdefvis = $fattrs => ---------- derive-deftly expansions dump for Enum::Struct from DbgVariants (end) ---------- ---------- derive-deftly expansions dump for Enum::Tuple.0 from DbgFields (start) ---------- top-level: $tname => Enum $ttype => Enum $tvis => $tgens => $tgnames => $twheres => $tdeftype => Enum $tdefgens => $tdefkwd => enum ${tdefvariants VARIANTS ..} => { VARIANTS .. } is_struct = false is_enum = true is_union = false tvis = false tgens = false $tattrs => #[allow(dead_code)] variant Tuple: $vname => Tuple $vtype => Enum :: Tuple $vpat => Enum :: Tuple { 0 : f_0, } ${vdefbody VNAME FIELDS ..} => VNAME(FIELDS ..), v_is_unit = false v_is_tuple = true v_is_named = false $vattrs => variant Tuple, field 0: $fname => 0 $ftype => usize $fvis => $fdefvis => $fpatname => f_0 $fdefine => fvis = false fdefvis = false $fattrs => ---------- derive-deftly expansions dump for Enum::Tuple.0 from DbgFields (end) ---------- ---------- derive-deftly expansions dump for Enum::Struct.field from DbgFields (start) ---------- top-level: $tname => Enum $ttype => Enum $tvis => $tgens => $tgnames => $twheres => $tdeftype => Enum $tdefgens => $tdefkwd => enum ${tdefvariants VARIANTS ..} => { VARIANTS .. } is_struct = false is_enum = true is_union = false tvis = false tgens = false $tattrs => #[allow(dead_code)] variant Struct: $vname => Struct $vtype => Enum :: Struct $vpat => Enum :: Struct { field : f_field, } ${vdefbody VNAME FIELDS ..} => VNAME { FIELDS .. }, v_is_unit = false v_is_tuple = false v_is_named = true $vattrs => variant Struct, field field: $fname => field $ftype => String $fvis => $fdefvis => $fpatname => f_field $fdefine => field : fvis = false fdefvis = false $fattrs => ---------- derive-deftly expansions dump for Enum::Struct.field from DbgFields (end) ---------- ---------- derive-deftly expansions dump for Enum::Tuple.0 from DbgNested (start) ---------- top-level: $tname => Enum $ttype => Enum $tvis => $tgens => $tgnames => $twheres => $tdeftype => Enum $tdefgens => $tdefkwd => enum ${tdefvariants VARIANTS ..} => { VARIANTS .. } is_struct = false is_enum = true is_union = false tvis = false tgens = false $tattrs => #[allow(dead_code)] variant Tuple: $vname => Tuple $vtype => Enum :: Tuple $vpat => Enum :: Tuple { 0 : f_0, } ${vdefbody VNAME FIELDS ..} => VNAME(FIELDS ..), v_is_unit = false v_is_tuple = true v_is_named = false $vattrs => variant Tuple, field 0: $fname => 0 $ftype => usize $fvis => $fdefvis => $fpatname => f_0 $fdefine => fvis = false fdefvis = false $fattrs => user-defined expansions: $FNAME => 0 user-defined conditions: IS_ENUM = true ---------- derive-deftly expansions dump for Enum::Tuple.0 from DbgNested (end) ---------- ---------- derive-deftly expansions dump for Enum::Struct.field from DbgNested (start) ---------- top-level: $tname => Enum $ttype => Enum $tvis => $tgens => $tgnames => $twheres => $tdeftype => Enum $tdefgens => $tdefkwd => enum ${tdefvariants VARIANTS ..} => { VARIANTS .. } is_struct = false is_enum = true is_union = false tvis = false tgens = false $tattrs => #[allow(dead_code)] variant Struct: $vname => Struct $vtype => Enum :: Struct $vpat => Enum :: Struct { field : f_field, } ${vdefbody VNAME FIELDS ..} => VNAME { FIELDS .. }, v_is_unit = false v_is_tuple = false v_is_named = true $vattrs => variant Struct, field field: $fname => field $ftype => String $fvis => $fdefvis => $fpatname => f_field $fdefine => field : fvis = false fdefvis = false $fattrs => user-defined expansions: $FNAME => field user-defined conditions: IS_ENUM = true ---------- derive-deftly expansions dump for Enum::Struct.field from DbgNested (end) ---------- ---------- derive-deftly expansions dump for Unit from Dbg (start) ---------- top-level: $tname => Unit $ttype => Unit $tvis => $tgens => $tgnames => $twheres => $tdeftype => Unit $tdefgens => $tdefkwd => struct ${tdefvariants VARIANTS ..} => VARIANTS .. is_struct = true is_enum = false is_union = false tvis = false tgens = false $tattrs => #[allow(dead_code)] value: $vname => $vtype => Unit $vpat => Unit {} ${vdefbody VNAME FIELDS ..} => FIELDS .. ; v_is_unit = true v_is_tuple = false v_is_named = false $vattrs => ---------- derive-deftly expansions dump for Unit from Dbg (end) ---------- ---------- derive-deftly expansions dump for Tuple from Dbg (start) ---------- top-level: $tname => Tuple $ttype => Tuple $tvis => $tgens => $tgnames => $twheres => $tdeftype => Tuple $tdefgens => $tdefkwd => struct ${tdefvariants VARIANTS ..} => VARIANTS .. is_struct = true is_enum = false is_union = false tvis = false tgens = false $tattrs => #[allow(dead_code)] value: $vname => $vtype => Tuple $vpat => Tuple { 0 : f_0, } ${vdefbody VNAME FIELDS ..} => (FIELDS ..); v_is_unit = false v_is_tuple = true v_is_named = false $vattrs => value, field 0: $fname => 0 $ftype => usize $fvis => $fdefvis => $fpatname => f_0 $fdefine => fvis = false fdefvis = false $fattrs => ---------- derive-deftly expansions dump for Tuple from Dbg (end) ---------- ---------- derive-deftly expansions dump for Struct from Dbg (start) ---------- top-level: $tname => Struct $ttype => Struct $tvis => $tgens => $tgnames => $twheres => $tdeftype => Struct $tdefgens => $tdefkwd => struct ${tdefvariants VARIANTS ..} => VARIANTS .. is_struct = true is_enum = false is_union = false tvis = false tgens = false $tattrs => #[allow(dead_code)] value: $vname => $vtype => Struct $vpat => Struct { field : f_field, } ${vdefbody VNAME FIELDS ..} => { FIELDS .. } v_is_unit = false v_is_tuple = false v_is_named = true $vattrs => value, field field: $fname => field $ftype => String $fvis => $fdefvis => $fpatname => f_field $fdefine => field : fvis = false fdefvis = false $fattrs => ---------- derive-deftly expansions dump for Struct from Dbg (end) ---------- ---------- derive-deftly expansion of ReferenceVersion for Tuple (start) ---------- #[derive(Deftly)] #[derive_deftly_adhoc] struct TupleReference < 'reference, F = (), > (& 'reference F,); impl < 'reference, F, > From < & 'reference Tuple :: < F > > for TupleReference < 'reference, F, > { fn from(ref_to_owned : & 'reference Tuple :: < F >) -> Self { match ref_to_owned { Tuple { 0 : f_0, } => TupleReference :: < F > { 0 : f_0, }, } } } impl < 'reference, F, > TupleReference < 'reference, F, > where F : Clone, { fn cloned(& self) -> Tuple :: < F > { match self { Self { 0 : f_0, } => Tuple :: < F > { 0 : (* * f_0).clone(), }, } } } ---------- derive-deftly expansion of ReferenceVersion for Tuple (end) ---------- ---------- derive-deftly expansion of ReferenceVersion for Struct (start) ---------- #[derive(Deftly)] #[derive_deftly_adhoc] struct StructReference < 'reference, F = (), > { field : & 'reference F, } impl < 'reference, F, > From < & 'reference Struct :: < F > > for StructReference < 'reference, F, > { fn from(ref_to_owned : & 'reference Struct :: < F >) -> Self { match ref_to_owned { Struct { field : f_field, } => StructReference :: < F > { field : f_field, }, } } } impl < 'reference, F, > StructReference < 'reference, F, > where F : Clone, { fn cloned(& self) -> Struct :: < F > { match self { Self { field : f_field, } => Struct :: < F > { field : (* * f_field).clone(), }, } } } ---------- derive-deftly expansion of ReferenceVersion for Struct (end) ---------- ---------- derive-deftly expansion of ReferenceVersion for Enum (start) ---------- #[derive(Deftly)] #[derive_deftly_adhoc] enum EnumReference < 'reference, F = (), > { Unit, Tuple(& 'reference F,), Struct { field : & 'reference F, }, } impl < 'reference, F, > From < & 'reference Enum :: < F > > for EnumReference < 'reference, F, > { fn from(ref_to_owned : & 'reference Enum :: < F >) -> Self { match ref_to_owned { Enum :: Unit {} => EnumReference :: Unit :: < F > {}, Enum :: Tuple { 0 : f_0, } => EnumReference :: Tuple :: < F > { 0 : f_0, }, Enum :: Struct { field : f_field, } => EnumReference :: Struct :: < F > { field : f_field, }, } } } impl < 'reference, F, > EnumReference < 'reference, F, > where F : Clone, F : Clone, { fn cloned(& self) -> Enum :: < F > { match self { Self :: Unit {} => Enum :: Unit :: < F > {}, Self :: Tuple { 0 : f_0, } => Enum :: Tuple :: < F > { 0 : (* * f_0).clone(), }, Self :: Struct { field : f_field, } => Enum :: Struct :: < F > { field : (* * f_field).clone(), }, } } } ---------- derive-deftly expansion of ReferenceVersion for Enum (end) ---------- ---------- derive-deftly expansions dump for EnumReference (start) ---------- top-level: $tname => EnumReference $ttype => EnumReference :: < 'reference, F, > $tvis => $tgens => 'reference, F, $tgnames => 'reference, F, $twheres => $tdeftype => EnumReference < 'reference, F = (), > $tdefgens => 'reference, F = (), $tdefkwd => enum ${tdefvariants VARIANTS ..} => { VARIANTS .. } is_struct = false is_enum = true is_union = false tvis = false tgens = true $tattrs => variant Unit: $vname => Unit $vtype => EnumReference :: Unit :: < 'reference, F, > $vpat => EnumReference :: Unit {} ${vdefbody VNAME FIELDS ..} => VNAME FIELDS .. , v_is_unit = true v_is_tuple = false v_is_named = false $vattrs => variant Tuple: $vname => Tuple $vtype => EnumReference :: Tuple :: < 'reference, F, > $vpat => EnumReference :: Tuple { 0 : f_0, } ${vdefbody VNAME FIELDS ..} => VNAME(FIELDS ..), v_is_unit = false v_is_tuple = true v_is_named = false $vattrs => variant Tuple, field 0: $fname => 0 $ftype => & 'reference F $fvis => $fdefvis => $fpatname => f_0 $fdefine => fvis = false fdefvis = false $fattrs => variant Struct: $vname => Struct $vtype => EnumReference :: Struct :: < 'reference, F, > $vpat => EnumReference :: Struct { field : f_field, } ${vdefbody VNAME FIELDS ..} => VNAME { FIELDS .. }, v_is_unit = false v_is_tuple = false v_is_named = true $vattrs => variant Struct, field field: $fname => field $ftype => & 'reference F $fvis => $fdefvis => $fpatname => f_field $fdefine => field : fvis = false fdefvis = false $fattrs => ---------- derive-deftly expansions dump for EnumReference (end) ---------- work/tests/stderr/stderr-lib.rs0000664000000000000000000000157314763657303014036 0ustar #![allow(clippy::style, clippy::complexity)] // See tests/stderr/Cargo.toml, under `[features]` #![cfg(not(feature = "disable"))] //! See `tests/stderr.rs` #[cfg(feature = "enable-main")] #[path = "../ui/badoptions.rs"] mod badoptions; #[cfg(feature = "enable-main")] #[path = "../ui/dbgdump.rs"] mod dbgdump; // Only compare the output using the recent versions, since the // minimal-versions produce different output // (mostly, different formatting for `$foo`, `$ foo`). #[cfg(feature = "enable-recent")] #[path = "../expand/dbg-all-keywords.rs"] mod dbg_all_keywords; // Again, minimal-versions produces slightly different output #[cfg(feature = "enable-recent")] #[path = "../expand/ref-version.rs"] #[macro_use] mod ref_version; #[cfg(feature = "enable-recent")] mod dbg_recent { derive_deftly::derive_deftly_adhoc! { EnumReference: $dbg_all_keywords } } work/tests/stderr.rs0000664000000000000000000001352214763657303011764 0ustar #![cfg(feature = "ui")] //! Tests of output to actual stderr //! //! Checks that our test cases //! produce the expected direct output to stderr. //! //! Direct stderr output is from things like //! the `dbg` template option. //! //! We reuse some of the `trybuild`'s ui test cases, from `tests/ui/`; //! see `tests/stderr/stderr-lib.rs` for the list. //! //! We must do this separately because `trybuild`'s `*.stderr` files //! do not actually contain real stderr output. //! The contents of those files is reconstructed from //! rustc's JSON message output, which includes only actual compiler errors, //! including from `compile_error!` and `.into_compile_error()`. //! It does *not* include anything printed by `eprintln` in a proc macro. //! `trybuild` silently discards that. //! //! So we reimplement, effectively, some of what `trybuild` does, here. //! //! *All* the output from all the test cases listed in `stderr-lib.rs` //! is concatenated into the output, in order. //! That avoids us having multiple test crates like this one. /// /// `combined.real-stderr` does *not* contain compiler messages. /// We suppress those, here, and rely on the `ui` tests to check them. use std::env; use std::ffi::OsString; use std::fs::{self, File}; use std::process::{self, Command}; /// Get `name` from the environment, sensibly fn env(name: &str) -> Option { env::var_os(name) .map(OsString::into_string) .transpose() .unwrap() } fn one_collection(coll: &str, compiles: Result<(), ()>) { let outer_cwd = env::current_dir().unwrap(); let outer_cwd = outer_cwd.to_str().unwrap(); eprintln!("outer cwd {}", outer_cwd); let outer_manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); eprintln!("outer manifest dir {}", outer_manifest_dir); let src_root = outer_manifest_dir.rsplit_once('/').unwrap().0; eprintln!("src root {}", src_root); let build_cwd = outer_cwd.rsplit_once('/').unwrap().0; eprintln!("build cwd {}", build_cwd); let inner_cwd = format!("{}/target/tests/derive-deftly-stderr-{}", build_cwd, coll); eprintln!("inner cwd {}", inner_cwd); let stderr_file = format!("{}/stderr/{}.real-stderr", outer_manifest_dir, coll); let stderr_file_new = format!("{}.new", stderr_file); eprintln!("stderr file {}", &stderr_file); let stdout_file = format!("{}/messages-stdout.jsor", inner_cwd); eprintln!("stdout file {}", &stdout_file); //Command::new("sh").args(["-xec", "env | sort"]).status().unwrap(); fs::create_dir_all(&inner_cwd).unwrap(); let command = env("CARGO").unwrap_or_else(|| "cargo".into()); let mut args = vec![ "check".into(), format!("--manifest-path={}/stderr/Cargo.toml", outer_manifest_dir), "--target-dir=target".into(), "--message-format=json".into(), "--quiet".into(), "--no-default-features".into(), format!("--features=enable-{}", coll), ]; let xoptions = [ // Allows CI to pass --locked "STDERRTEST_CARGO_OPTIONS", // https://diziet.dreamwidth.org/tag/nailing-cargo "NAILINGCARGO_CARGO_OPTIONS", ] .iter() .cloned() .find_map(env); if let Some(xoptions) = xoptions { args.extend(xoptions.split_ascii_whitespace().map(Into::into)) } eprint!("running {}", &command); for arg in &args { eprint!(" {}", &arg); } eprintln!(); let mut command = Command::new(command); command.current_dir(&inner_cwd); command.stdout(File::create(&stdout_file).unwrap()); command.stderr(File::create(stderr_file_new).unwrap()); command.args(args); let status = command.status().unwrap(); let status_ok = status.success().then(|| ()).ok_or(()); match (status_ok, compiles) { (Ok(()), Ok(())) => {} (Err(()), Err(())) => {} (Ok(()), Err(())) => panic!("compliation unexpectedly succeeded"), (Err(()), Ok(())) => { let rstatus = Command::new("jq") .arg(".message.rendered") .stdin(File::open(&stdout_file).unwrap()) .status() .unwrap(); if !rstatus.success() { eprintln!("(failed to print message jq: {})", status); } panic!("compliation failed: {}", status); } } eprintln!("exit status: {} (error, as expected)", status); let mut scriptlet = Command::new("bash"); scriptlet.arg("-ec"); scriptlet.arg( r#" set -o pipefail file="$1"; shift perl -e ' @l = ; @l = grep { !( m{^error: could not compile `derive-deftly-stderr-test} || m{^To learn more, run the command again with --verbose} ) } @l; while (@l && $l[$#l] !~ m/\S/) { pop @l; } print @l or die $!; ' <"$file.new" >"$file.new.tmp" mv -- "$file.new.tmp" "$file.new" if diff -u -- "$file" "$file.new" >&2; then echo >&2 'stderr: no changes to stderr output.' rm -f "$file.new" exit 0 fi case "$STDERRTEST" in overwrite) echo >&2 '*** changes to stderr output, installing! ***' mv -- "$file.new" "$file" ;; *) echo >&2 '*** changes to stderr output! ***' echo >&2 'set STDERRTEST=overwrite to declare it good' exit 1 ;; esac "#, ); scriptlet.args(["scriptlet", &stderr_file]); match scriptlet.status().unwrap() { status if status.code() == Some(0) => {} status if status.code() == Some(1) => process::exit(1), other => panic!("scriptlet crashed {}", other), } } #[test] fn stderr_main() { one_collection("main", Err(())); } #[cfg(all(feature = "recent"))] #[test] fn stderr_recent() { one_collection("recent", Ok(())); } work/tests/tests.rs0000664000000000000000000001306714763657303011627 0ustar #![allow(clippy::style, clippy::complexity, clippy::perf)] //! Tests for derive-deftly //! //! **Internal, unpublished crate.** //! //! Separating these tests into their own crate arranges that they //! can go through the "front door" of `use derive_deftly::...`, //! and in particular makes the cross-crate tests uniform with the others. //! //! ## Invoking the tests //! //! When running the full test suite, //! you should use a particular version of Nightly Rust: //! ```text //! rustup toolchain add nightly-2025-02-10 //! cargo +nightly-2025-02-10 test --locked --workspace --all-features //! ``` //! With other versions, the pretty printing of the macro expansions, //! or the results of standard library macros, can change, //! causing the tests to break. //! //! After your first run, it will probably be helpful to say //! ```text //! CARGO_NET_OFFLINE=true cargo +nightly-2025-02-10 test --locked --workspace --all-features //!``` //! This is because otherwise cargo //! will uselessly re-update the cargo index, //! when it is reinvoked by some of the tests. //! //! ## Updating the expected outputs //! //! Many of the test cases compare an actual with expected output. //! For example, an expected macro expansion, or error message. //! //! If this is expected //! (for example, you added to the tests, or fixed a bug) //! can update the expected output files from the actual output. //! //! ```text //! TRYBUILD=overwrite MACROTEST=overwrite STDERRTEST=overwrite \ //! CARGO_NET_OFFLINE=true cargo +nightly-2025-02-10 test --locked --workspace --all-features //! ``` //! //! *Check that the actual output is as desired!* //! This is easily done with `git diff` before committing. //! //! ### Slight discrepancies in output, due to reformatting unfaithfulness //! //! The outputs in `tests/expand/` come from `cargo expand`, //! which reformats them for legibility. //! This reformatting is not always 100% faithful. //! For example, it can insert or remove `::` tokens. //! //! The output from `dbg_all_keywords` is completely faithful. //! This appears in `tests/stderr/recent.real-stderr`, for example. //! //! See //! [#13](https://gitlab.torproject.org/Diziet/rust-derive-deftly/-/issues/13) //! for some more discussion. //! //! ## Classes of test in the derive-deftly workspace //! //! Testing proc macros is not entirely straightforward, //! so there are multiple classes of test with different approaches. //! //! ### `tests/expand/*.rs` //! //! - Run with [`trybuild`], and expected to pass. //! - Expanded with `macrotest`, compared with `expand/*.expanded.rs`. //! //! Invoked from `tests/macrotest.rs`, //! using [`list_expand_test_paths`] //! and [`list_expand_test_paths_for_macrotest`]. //! //! Tests with `recent` somewhere in their name are only run //! if the `recent` cargo feature is enabled for `derive-deftly-tests`. //! This is enabled in CI for the newer compilers, but not for the MSRV. //! //! ### `tests/ui/*.rs` //! //! - Run with trybuild and expected to fail. //! - Errors compared with `tests/ui/*.stderr`. //! //! Invoked from `tests/trybuild.rs`. //! //! Some of these `.rs` files are reused for `tests/stderr/`. //! //! #### `tests/minimal-ui/*.rs` //! //! As above, but used when feature `full` isn't enabled. //! This just tests errors from features being disabled. //! (In the tests, we include `beta` in `full`.) //! //! ### `tests/stderr/` //! //! Test cases listed in `tests/stderr/stderr-lib.rs`. //! //! - Compile attempted, and stderr captured //! - Output compared with `tests/stderr/combined.real-stderr` //! //! Invoked from `tests/stderr.rs`, //! which builds the crate `derive-deftly-stderr-tests`. //! See the doc comment there for details and rationale. //! //! ### `tests/pub-a/`, `tests/pub-b/` //! //! Tests of cross-crate exports of macros and templates. //! `pub-a` exports things, and `pub-b` imports and uses them. //! //! `pub-a` uses a special `bizarre` version of derive-deftly, //! to test that the right template expander is used in each case. //! See the [`pub-b` doc comment](../pub_b/index.html) for details. //! //! ### `tests/compat/` //! //! Tests compatibility with old, published, versions of derive-deftly. //! See `tests/compat/README.md`. //! //! ### The normal-ish `#[cfg(test)]` modules listed in here `tests/tests.rs` //! //! Each module is compiled, and its `#[test]` functions are run. //! //! ### `tests/directly/` //! //! Tests that go through the "back door", //! to use the innards of derive-deftly. //! //! Currently this is the tests in `directly::check_examples` //! which extract examples from `doc/reference.md` //! and check that they match real output. // We don't want to have to cfg-mark all the imports #![cfg_attr( not(all(test, feature = "full", feature = "ui", feature = "recent")), allow(unused_imports, dead_code) )] use std::cell::RefCell; use std::collections::HashMap; use std::fmt::Debug; use std::fmt::Display; use std::fs::File; use std::io::{BufRead, BufReader}; use std::iter; use std::path::PathBuf; use std::rc::Rc; use easy_ext::ext; use educe::Educe; use itertools::Itertools; use regex::Regex; use static_assertions::assert_not_impl_any; pub mod tutils; pub use tutils::*; // These mostly serve to avoid rustdoc warnings from directly::macros, // which is macros.rs re-built outside the proc macro system. #[allow(unused_imports)] use derive_deftly::{define_derive_deftly, derive_deftly_adhoc, Deftly}; //---------- modules containing straightforwrad `#[test]` tests ---------- #[cfg(test)] mod list_names; #[cfg(test)] mod modules; // This is special, and contains tests that use a clone of the d-d-m crate #[cfg(not(doc))] mod directly; work/tests/trybuild.rs0000664000000000000000000000244614763657303012322 0ustar #[allow(unused_imports)] use derive_deftly_tests::*; // We don't run this unless the CI explicitly enables it. // That's done only for the tests with all d-d features enabled, // Ie, the ones that aren't -minfeatures. // // So there is n testing of what feature controls what. // That's OK, we're not going to have very many features. // // The basic tests in tests.rs still run of course. #[test] #[cfg(feature = "full")] pub fn run_pass_expand() { let t = trybuild::TestCases::new(); for path in list_expand_test_paths() { t.pass(path); } } // Probably, don't add a straightforward run-pass/ directory // and corresponding call to trybuild, here. // // Instead, put the file in tests/, with `#[test]` annotations, // and add a `mod` line to `tests/tests.rs`, as with `list_names.rs`. // // We don't run this unless the CI explicitly enables it. // That's one only for our main test with the pinned compiler, // and all d-d features enabled. // // We basically trust that the features-disabled fallback code // for handling the disabled feature is good enough. #[cfg(feature = "ui")] #[test] pub fn ui() { let t = trybuild::TestCases::new(); #[allow(unused_variables)] let pat = "minimal-ui/*.rs"; #[cfg(feature = "full")] let pat = "ui/*.rs"; t.compile_fail(pat); } work/tests/tutils.rs0000664000000000000000000000671114763657303012007 0ustar //! utilities and common code for testingy use super::*; #[ext(DebugExt)] pub impl T { fn to_debug(&self) -> String { format!("{:?}", self) } } /// Compile `re` into a [`Regex`], with cacheing // Not using lazy_regex because we sometimes make our // regexps with format! so they aren't static #[cfg(feature = "recent")] // Doesn't work on 1.54; Mutex::new isn't const pub fn compile_re_cached(re: &str) -> Regex { use std::sync::Mutex; static CACHE: Mutex>> = Mutex::new(None); let mut guard = CACHE.lock().unwrap(); let cache = guard.get_or_insert_with(|| Default::default()); match cache.get(re) { None => { let re = Regex::new(re).expect(&format!("bad regexp {re}")); cache.entry(re.to_string()).or_insert(re) } Some(re) => re, } .clone() } /// `fn re!(l: &str) -> Regex` #[macro_export] macro_rules! re { { $re:expr $(,)? } => { compile_re_cached($re.as_ref()) } } /// `fn m!(l: &str, re: &str) -> bool`: does regexp `re` match in `l` ? #[macro_export] macro_rules! m { { $l:expr, $re:expr $(,)? } => { re!($re).is_match(&$l) } } /// `fn mc!(l: &str, re: &str) -> Option<(CAP,...)>`: regexp captures? /// /// `(CAP,...)` is a tuple of `String`. #[macro_export] macro_rules! mc { { $l:expr, $re:expr $(,)? } => { re!($re) .captures(&$l) .map(|caps| { let caps = caps .iter() .map(|m| m.map(|m| m.as_str().to_owned()).unwrap_or_default()) .collect_vec(); let len = caps.len(); caps .into_iter() .skip(1) .collect_tuple() .unwrap_or_else(|| { panic!("wrong # matches: got {} from {}", len, $re) }) }) } } const EXPAND_TEST_GLOB: &str = "expand/*.rs"; const EXPAND_MACROTEST_IGNORE_GLOB: &str = "*.expanded.rs"; /// List the test cases in tests/expand/*.rs /// /// Filters out tests with the word `recent` in, /// unless the `recent` cargo feature is enabled. /// These are tests that won't work with our MSRV. /// /// Filters out *.expanded.rs. pub fn list_expand_test_paths() -> Vec { list_expand_general(&|_| false) } /// List the test cases in tests/expand/*.rs, for `macrotest` /// /// Like `list_expand_test_paths`, but if the only thing we filter /// out is `*.expanded.rs`, simply returns the `expand/*.rs` glob. /// /// Then macrotest will automatically ignore `*.expanded.rs`. /// We do this because macrotest is faster and less noisy /// if we run it once with a single glob. pub fn list_expand_test_paths_for_macrotest() -> Vec { list_expand_general(&|ign| ign == EXPAND_MACROTEST_IGNORE_GLOB) } fn list_expand_general(will_filter: &dyn Fn(&str) -> bool) -> Vec { let ignores = [ EXPAND_MACROTEST_IGNORE_GLOB, #[cfg(not(feature = "recent"))] "*recent*", ]; if ignores.iter().cloned().all(will_filter) { return iter::once(EXPAND_TEST_GLOB.into()).collect(); } let ignores: Vec<_> = ignores .iter() .map(|pat| glob::Pattern::new(pat).unwrap()) .collect(); glob::glob(EXPAND_TEST_GLOB) .unwrap() .filter_map(move |path| { let path = path.unwrap(); if ignores.iter().any(|pat| pat.matches_path(&path)) { return None; } Some(path) }) .collect() } work/tests/ui/0000775000000000000000000000000014763657303010525 5ustar work/tests/ui/badargs.rs0000664000000000000000000000121614763657303012476 0ustar use derive_deftly::{define_derive_deftly, derive_deftly_adhoc, Deftly}; #[derive(Deftly)] #[derive_deftly_adhoc] struct DataType { foo: u8, #[deftly(hash(skip))] bar: Vec, } derive_deftly_adhoc! { DataType: fn $unknown() { } $( ) ${for fields { f ${fname junk}() { } }} ${if false { } else { } forbidden} ${if tmeta(some_path) as lit { }} } define_derive_deftly! { Broken: type Alias = ${ttype $junk}; } #[derive(Deftly)] #[derive_deftly(Broken)] #[derive_deftly_adhoc] struct ForBroken; derive_deftly_adhoc! { DataType: ${if approx_equal(1,2,3) {}} } fn main() {} work/tests/ui/badargs.stderr0000664000000000000000000000200214763657303013347 0ustar error: unknown derive-deftly keyword --> ui/badargs.rs:14:9 | 14 | fn $unknown() { } | ^^^^^^^ error: no contained expansion field determined what to repeat here --> ui/badargs.rs:16:6 | 16 | $( | ______^ 17 | | ) | |_____^ error: unexpected arguments to expansion keyword --> ui/badargs.rs:20:19 | 20 | f ${fname junk}() { } | ^^^^ error: unexpected arguments to expansion keyword --> ui/badargs.rs:23:29 | 23 | ${if false { } else { } forbidden} | ^^^^^^^^^ error: expected curly braces --> ui/badargs.rs:25:27 | 25 | ${if tmeta(some_path) as lit { }} | ^^ error: unexpected arguments to expansion keyword --> ui/badargs.rs:31:26 | 31 | type Alias = ${ttype $junk}; | ^ error: approx_equal() requires two comma-separated arguments --> ui/badargs.rs:42:10 | 42 | ${if approx_equal(1,2,3) {}} | ^^^^^^^^^^^^ work/tests/ui/badcontext.rs0000664000000000000000000000102414763657303013223 0ustar use derive_deftly::{define_derive_deftly, derive_deftly_adhoc, Deftly}; define_derive_deftly! { /// This is fundamentally misconceived FieldNames expect expr: [ $( stringify!($fname), ) ] } #[derive(Deftly)] #[derive_deftly(FieldNames)] #[derive_deftly_adhoc] struct DataType {} derive_deftly_adhoc! { // This is going to be a syntax error, since it expands to an expr DataType expect expr: ${for fields { 1 + }} 0 } derive_deftly_adhoc! { DataType expect items: ${ignore $fname} } fn main() {} work/tests/ui/badcontext.stderr0000664000000000000000000000145614763657303014113 0ustar error: predefined templates must always expand to items --> ui/badcontext.rs:5:23 | 5 | FieldNames expect expr: | ^^^^ error: macro expansion ignores `0` and any tokens following --> ui/badcontext.rs:17:27 | 9 | #[derive(Deftly)] | ------ caused by the macro expansion here ... 17 | ${for fields { 1 + }} 0 | ^ | = note: the usage of `::derive_deftly::derive_deftly_engine!` is likely invalid in item context error: must be within a field (so, in a repeat group) --> ui/badcontext.rs:22:15 | 22 | ${ignore $fname} | ^^^^^ error: cannot find macro `derive_deftly_template_FieldNames` in this scope --> ui/badcontext.rs:10:17 | 10 | #[derive_deftly(FieldNames)] | ^^^^^^^^^^ work/tests/ui/badinternals.rs0000664000000000000000000000262614763657303013547 0ustar // Tests that cause internal macros to generate their "incompatible versions" // advice. Actually generating this advice in a more realistic scenario, // rather than just directly invoking the macros, is quite hard. So this // test is not particularly realistic. But it does exercise the error // generation code paths. use derive_deftly::{define_derive_deftly, Deftly}; #[derive(Deftly)] #[derive_deftly_adhoc] struct Driver; define_derive_deftly! { Template: } derive_deftly_driver_Driver! { GARBAGE -> DRIVER } derive_deftly_template_Template! { GARBAGE -> TEMPLATE } derive_deftly::derive_deftly_engine! { GARBAGE -> INNARDS } // OpCompatVersions 1.0, ie our own, and the earliest released derive_deftly::derive_deftly_engine! { { pub struct StructName {} } [1 0] ( ) { pub struct OptOk0; } ( crate; [] Template; ) [] [] RANDOM STUFF } // OpCompatVersions 1.200, some imaginary future compatible one derive_deftly::derive_deftly_engine! { { pub struct StructName {} } [1 200] ( ) { pub struct OptOk200; } ( crate; [] Template; ) [] [] RANDOM STUFF } // OpCompatVersions 200.1, some imaginary future incompatible one derive_deftly::derive_deftly_engine! { { pub struct StructName {} } [200 0] ( ) { OUGHT NOT TO BE EXPANDED; } ( crate; [] Template; ) [] [] RANDOM STUFF } fn main() { let _ = OptOk0; let _ = OptOk200; } work/tests/ui/badinternals.stderr0000664000000000000000000000506514763657303014426 0ustar error: wrong input to derive-deftly driver inner macro derive_deftly_driver_Driver; might be due to incompatible derive-deftly versions(s) --> ui/badinternals.rs:9:10 | 9 | #[derive(Deftly)] | ^^^^^^ ... 17 | derive_deftly_driver_Driver! { GARBAGE -> DRIVER } | -------------------------------------------------- in this macro invocation | = note: this error originates in the macro `derive_deftly_driver_Driver` (in Nightly builds, run with -Z macro-backtrace for more info) error: wrong input to derive-deftly template macro derive_deftly_template_Template; might be due to incompatible derive-deftly versions(s) --> ui/badinternals.rs:13:1 | 13 | / define_derive_deftly! { 14 | | Template: 15 | | } | |_^ ... 18 | derive_deftly_template_Template! { GARBAGE -> TEMPLATE } | -------------------------------------------------------- in this macro invocation | = note: this error originates in the macro `derive_deftly_template_Template` (in Nightly builds, run with -Z macro-backtrace for more info) error: bad input to derive_deftly_engine inner template expansion proc macro; might be due to incompatible derive-deftly versions(s) --> ui/badinternals.rs:19:1 | 19 | derive_deftly::derive_deftly_engine! { GARBAGE -> INNARDS } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | = note: this error originates in the macro `derive_deftly::derive_deftly_engine` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected curly braces --> ui/badinternals.rs:19:40 | 19 | derive_deftly::derive_deftly_engine! { GARBAGE -> INNARDS } | ^^^^^^^ error: bad input to derive_deftly_engine inner template expansion proc macro; might be due to incompatible derive-deftly versions(s) --> ui/badinternals.rs:44:1 | 44 | / derive_deftly::derive_deftly_engine! { 45 | | { pub struct StructName {} } 46 | | [200 0] 47 | | ( ) ... | 51 | | RANDOM STUFF 52 | | } | |_^ | = note: this error originates in the macro `derive_deftly::derive_deftly_engine` (in Nightly builds, run with -Z macro-backtrace for more info) error: Incompatible major version for AOPTIONS (driver 200, template/engine 1) --> ui/badinternals.rs:44:1 | 44 | / derive_deftly::derive_deftly_engine! { 45 | | { pub struct StructName {} } 46 | | [200 0] 47 | | ( ) ... | 51 | | RANDOM STUFF 52 | | } | |_^ | = note: this error originates in the macro `derive_deftly::derive_deftly_engine` (in Nightly builds, run with -Z macro-backtrace for more info) work/tests/ui/badmeta.rs0000664000000000000000000000621214763657303012471 0ustar use derive_deftly::{define_derive_deftly, derive_deftly_adhoc, Deftly}; #[derive(Deftly)] #[derive_deftly_adhoc] #[deftly(wrong = "{")] #[deftly(dot = ".")] #[deftly(several = "first")] #[deftly(several = "second")] #[deftly(several_inner(several = "first", several = "second"))] #[deftly(integer = "42", with_suffix = "42"x)] #[deftly(int128 = "0x12345678_12345678_12345678_12345678")] struct DataType {} derive_deftly_adhoc! { DataType: const K: () = ${tmeta(wrong) as expr} } // Invoke derive_deftly_adhoc! a second time, because (empirically) the // attempt to parse "{" causes some kind of nonlocal exit: // syn::LitStr::parse is called but doesn't return. And then, something // arranges to report only the *last* such error. But we want to see // all the errors, so invoke the macro separately. derive_deftly_adhoc! { DataType: struct ${paste Bad ${tmeta(dot) as str}}; // fails because `dot = ...` is not helpful struct ${paste Bad ${tmeta(dot(has_equals)) as str}}; struct ${paste Bad ${tmeta(several) as str}}; struct ${paste Bad ${tmeta(several_inner(several)) as str}}; } // Something somewhere deduplicates errors, so if we want to see them // all we need to re-invoke derive-deftly: derive_deftly_adhoc! { DataType: // fails because `barelit("42")` is not helpful struct ${paste Bad ${tmeta(barelit(is_value)) as str}}; } derive_deftly_adhoc! { DataType: // fails because `several_inner(...)` is not helpful struct ${paste Bad ${tmeta(several_inner) as str}}; } #[derive(Deftly)] #[derive_deftly_adhoc] #[deftly("forbidden")] struct TopLevelMetaLitForbidden; derive_deftly_adhoc! { TopLevelMetaLitForbidden: } #[derive(Deftly)] #[derive_deftly_adhoc] #[deftly(value = wrong)] struct MetaValueMustBeLit; derive_deftly_adhoc! { MetaValueMustBeLit: } #[derive(Deftly)] #[derive_deftly_adhoc] #[deftly(barelit("42"))] struct WithBareLit; #[derive(Deftly)] #[derive_deftly_adhoc] #[deftly(multi("1", "2"))] struct WithBareLitMulti; derive_deftly_adhoc! { WithBareLit: struct ${paste Bad ${tmeta(barelit) as str}}; } derive_deftly_adhoc! { WithBareLitMulti: struct ${paste Bad ${tmeta(multi) as str}}; } define_derive_deftly! { TestFunkyPath: struct ${paste Bad ${tmeta(std::cell::Cell::::ne)w as str}}; } #[derive(Deftly)] #[derive_deftly_adhoc] #[derive_deftly(TestFunkyPath)] #[deftly(std::cell::Cell::::new)] struct WithFunkyPath; derive_deftly_adhoc! { WithFunkyPath: } #[derive(Deftly)] #[derive_deftly(TestFunkyPath)] struct WithoutfunkyPath; define_derive_deftly! { WrongRequestSyntax: $tmeta(something) ${if tmeta} ${tmeta(something) unknown_option} ${if tmeta(something) unknown_option {}} } derive_deftly_adhoc! { DataType: ${tmeta(dot)} // forgot `.. as`, so `"."` not even parsed } derive_deftly_adhoc! { DataType: ${if approx_equal("42", ${tmeta(with_suffix) as str}) {}} } derive_deftly_adhoc! { DataType: ${if approx_equal(0x12345678_12345678_12345678_aaaaaaaa, ${tmeta(int128) as token_stream}) {}} } derive_deftly_adhoc! { DataType: ${fmeta(missing) default { 42 }} } fn main() {} work/tests/ui/badmeta.stderr0000664000000000000000000003526314763657303013360 0ustar error: cannot parse string into token stream --> ui/badmeta.rs:3:10 | 3 | #[derive(Deftly)] | ^^^^^^ ... 14 | / derive_deftly_adhoc! { 15 | | DataType: 16 | | 17 | | const K: () = ${tmeta(wrong) as expr} 18 | | } | |_- in this macro invocation | = note: this error originates in the macro `::derive_deftly::derive_deftly_engine` which comes from the expansion of the macro `derive_deftly_adhoc` (in Nightly builds, run with -Z macro-backtrace for more info) error: constructed identifier "Bad." is invalid --> ui/badmeta.rs:28:14 | 28 | struct ${paste Bad ${tmeta(dot) as str}}; | ^^^^^ error: probably-invalid input to identifier pasting --> ui/badmeta.rs:6:16 | 6 | #[deftly(dot = ".")] | ^^^ ... 25 | / derive_deftly_adhoc! { 26 | | DataType: 27 | | 28 | | struct ${paste Bad ${tmeta(dot) as str}}; ... | 33 | | struct ${paste Bad ${tmeta(several_inner(several)) as str}}; 34 | | } | |_- in this macro invocation | = note: this error originates in the macro `derive_deftly_driver_DataType` which comes from the expansion of the macro `derive_deftly_adhoc` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected a list with sub-attributes, found a simple value --> ui/badmeta.rs:6:10 | 6 | #[deftly(dot = ".")] | ^^^ ... 25 | / derive_deftly_adhoc! { 26 | | DataType: 27 | | 28 | | struct ${paste Bad ${tmeta(dot) as str}}; ... | 33 | | struct ${paste Bad ${tmeta(several_inner(several)) as str}}; 34 | | } | |_- in this macro invocation | = note: this error originates in the macro `derive_deftly_driver_DataType` which comes from the expansion of the macro `derive_deftly_adhoc` (in Nightly builds, run with -Z macro-backtrace for more info) error: attribute value expanded, but no suitable value in data structure definition (in this data structure) --> ui/badmeta.rs:4:1 | 4 | / #[derive_deftly_adhoc] 5 | | #[deftly(wrong = "{")] 6 | | #[deftly(dot = ".")] 7 | | #[deftly(several = "first")] ... | 11 | | #[deftly(int128 = "0x12345678_12345678_12345678_12345678")] 12 | | struct DataType {} | |_________________^ ... 25 | / derive_deftly_adhoc! { 26 | | DataType: 27 | | 28 | | struct ${paste Bad ${tmeta(dot) as str}}; ... | 33 | | struct ${paste Bad ${tmeta(several_inner(several)) as str}}; 34 | | } | |_- in this macro invocation | = note: this error originates in the macro `derive_deftly_driver_DataType` which comes from the expansion of the macro `derive_deftly_adhoc` (in Nightly builds, run with -Z macro-backtrace for more info) error: attribute value expanded, but no suitable value in data structure definition (expansion) --> ui/badmeta.rs:30:26 | 30 | struct ${paste Bad ${tmeta(dot(has_equals)) as str}}; | ^^^^^^^^^^^^^^^^^^^^ error: tried to expand just attribute value, but it was specified multiple times (first occurrence) --> ui/badmeta.rs:7:10 | 7 | #[deftly(several = "first")] | ^^^^^^^ ... 25 | / derive_deftly_adhoc! { 26 | | DataType: 27 | | 28 | | struct ${paste Bad ${tmeta(dot) as str}}; ... | 33 | | struct ${paste Bad ${tmeta(several_inner(several)) as str}}; 34 | | } | |_- in this macro invocation | = note: this error originates in the macro `derive_deftly_driver_DataType` which comes from the expansion of the macro `derive_deftly_adhoc` (in Nightly builds, run with -Z macro-backtrace for more info) error: tried to expand just attribute value, but it was specified multiple times (second occurrence) --> ui/badmeta.rs:8:10 | 8 | #[deftly(several = "second")] | ^^^^^^^ ... 25 | / derive_deftly_adhoc! { 26 | | DataType: 27 | | 28 | | struct ${paste Bad ${tmeta(dot) as str}}; ... | 33 | | struct ${paste Bad ${tmeta(several_inner(several)) as str}}; 34 | | } | |_- in this macro invocation | = note: this error originates in the macro `derive_deftly_driver_DataType` which comes from the expansion of the macro `derive_deftly_adhoc` (in Nightly builds, run with -Z macro-backtrace for more info) error: tried to expand just attribute value, but it was specified multiple times (expansion) --> ui/badmeta.rs:32:26 | 32 | struct ${paste Bad ${tmeta(several) as str}}; | ^^^^^^^^^^^^^ error: tried to expand just attribute value, but it was specified multiple times (first occurrence) --> ui/badmeta.rs:9:24 | 9 | #[deftly(several_inner(several = "first", several = "second"))] | ^^^^^^^ ... 25 | / derive_deftly_adhoc! { 26 | | DataType: 27 | | 28 | | struct ${paste Bad ${tmeta(dot) as str}}; ... | 33 | | struct ${paste Bad ${tmeta(several_inner(several)) as str}}; 34 | | } | |_- in this macro invocation | = note: this error originates in the macro `derive_deftly_driver_DataType` which comes from the expansion of the macro `derive_deftly_adhoc` (in Nightly builds, run with -Z macro-backtrace for more info) error: tried to expand just attribute value, but it was specified multiple times (second occurrence) --> ui/badmeta.rs:9:43 | 9 | #[deftly(several_inner(several = "first", several = "second"))] | ^^^^^^^ ... 25 | / derive_deftly_adhoc! { 26 | | DataType: 27 | | 28 | | struct ${paste Bad ${tmeta(dot) as str}}; ... | 33 | | struct ${paste Bad ${tmeta(several_inner(several)) as str}}; 34 | | } | |_- in this macro invocation | = note: this error originates in the macro `derive_deftly_driver_DataType` which comes from the expansion of the macro `derive_deftly_adhoc` (in Nightly builds, run with -Z macro-backtrace for more info) error: tried to expand just attribute value, but it was specified multiple times (expansion) --> ui/badmeta.rs:33:26 | 33 | struct ${paste Bad ${tmeta(several_inner(several)) as str}}; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: attribute value expanded, but no value in data structure definition (in this data structure) --> ui/badmeta.rs:4:1 | 4 | / #[derive_deftly_adhoc] 5 | | #[deftly(wrong = "{")] 6 | | #[deftly(dot = ".")] 7 | | #[deftly(several = "first")] ... | 11 | | #[deftly(int128 = "0x12345678_12345678_12345678_12345678")] 12 | | struct DataType {} | |_________________^ ... 38 | / derive_deftly_adhoc! { 39 | | DataType: ... | 43 | | } | |_- in this macro invocation | = note: this error originates in the macro `derive_deftly_driver_DataType` which comes from the expansion of the macro `derive_deftly_adhoc` (in Nightly builds, run with -Z macro-backtrace for more info) error: attribute value expanded, but no value in data structure definition (expansion) --> ui/badmeta.rs:42:26 | 42 | struct ${paste Bad ${tmeta(barelit(is_value)) as str}}; | ^^^^^^^^^^^^^^^^^^^^^^ error: expected a leaf node, found a list with sub-attributes --> ui/badmeta.rs:9:10 | 9 | #[deftly(several_inner(several = "first", several = "second"))] | ^^^^^^^^^^^^^ ... 45 | / derive_deftly_adhoc! { 46 | | DataType: ... | 50 | | } | |_- in this macro invocation | = note: this error originates in the macro `derive_deftly_driver_DataType` which comes from the expansion of the macro `derive_deftly_adhoc` (in Nightly builds, run with -Z macro-backtrace for more info) error: attribute value expanded, but no suitable value in data structure definition (in this data structure) --> ui/badmeta.rs:4:1 | 4 | / #[derive_deftly_adhoc] 5 | | #[deftly(wrong = "{")] 6 | | #[deftly(dot = ".")] 7 | | #[deftly(several = "first")] ... | 11 | | #[deftly(int128 = "0x12345678_12345678_12345678_12345678")] 12 | | struct DataType {} | |_________________^ ... 45 | / derive_deftly_adhoc! { 46 | | DataType: ... | 50 | | } | |_- in this macro invocation | = note: this error originates in the macro `derive_deftly_driver_DataType` which comes from the expansion of the macro `derive_deftly_adhoc` (in Nightly builds, run with -Z macro-backtrace for more info) error: attribute value expanded, but no suitable value in data structure definition (expansion) --> ui/badmeta.rs:49:26 | 49 | struct ${paste Bad ${tmeta(several_inner) as str}}; | ^^^^^^^^^^^^^^^^^^^ error: expected identifier --> ui/badmeta.rs:54:10 | 54 | #[deftly("forbidden")] | ^^^^^^^^^^^ error: expected identifier --> ui/badmeta.rs:54:10 | 54 | #[deftly("forbidden")] | ^^^^^^^^^^^ 55 | struct TopLevelMetaLitForbidden; 56 | derive_deftly_adhoc! { TopLevelMetaLitForbidden: } | -------------------------------------------------- in this macro invocation | = note: this error originates in the macro `derive_deftly_driver_TopLevelMetaLitForbidden` which comes from the expansion of the macro `derive_deftly_adhoc` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected literal --> ui/badmeta.rs:60:18 | 60 | #[deftly(value = wrong)] | ^^^^^ error: expected literal --> ui/badmeta.rs:60:18 | 60 | #[deftly(value = wrong)] | ^^^^^ 61 | struct MetaValueMustBeLit; 62 | derive_deftly_adhoc! { MetaValueMustBeLit: } | -------------------------------------------- in this macro invocation | = note: this error originates in the macro `derive_deftly_driver_MetaValueMustBeLit` which comes from the expansion of the macro `derive_deftly_adhoc` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected identifier --> ui/badmeta.rs:66:18 | 66 | #[deftly(barelit("42"))] | ^^^^ error: expected identifier --> ui/badmeta.rs:71:16 | 71 | #[deftly(multi("1", "2"))] | ^^^ error: expected identifier --> ui/badmeta.rs:66:18 | 66 | #[deftly(barelit("42"))] | ^^^^ ... 74 | / derive_deftly_adhoc! { 75 | | WithBareLit: 76 | | struct ${paste Bad ${tmeta(barelit) as str}}; 77 | | } | |_- in this macro invocation | = note: this error originates in the macro `derive_deftly_driver_WithBareLit` which comes from the expansion of the macro `derive_deftly_adhoc` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected identifier --> ui/badmeta.rs:71:16 | 71 | #[deftly(multi("1", "2"))] | ^^^ ... 79 | / derive_deftly_adhoc! { 80 | | WithBareLitMulti: 81 | | struct ${paste Bad ${tmeta(multi) as str}}; 82 | | } | |_- in this macro invocation | = note: this error originates in the macro `derive_deftly_driver_WithBareLitMulti` which comes from the expansion of the macro `derive_deftly_adhoc` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected path segment after `::` --> ui/badmeta.rs:86:49 | 86 | struct ${paste Bad ${tmeta(std::cell::Cell::::ne)w as str}}; | ^ error: expected path segment after `::` --> ui/badmeta.rs:92:27 | 92 | #[deftly(std::cell::Cell::::new)] | ^ error: expected path segment after `::` --> ui/badmeta.rs:92:27 | 92 | #[deftly(std::cell::Cell::::new)] | ^ ... 95 | / derive_deftly_adhoc! { 96 | | WithFunkyPath: 97 | | } | |_- in this macro invocation | = note: this error originates in the macro `derive_deftly_driver_WithFunkyPath` which comes from the expansion of the macro `derive_deftly_adhoc` (in Nightly builds, run with -Z macro-backtrace for more info) error: missing parameters to expansion keyword (NB: argument must be within { }) --> ui/badmeta.rs:106:6 | 106 | $tmeta(something) | ^^^^^ error: missing parameters to condition --> ui/badmeta.rs:107:10 | 107 | ${if tmeta} | ^^^^^ error: unknown option in `${Xmeta }` --> ui/badmeta.rs:109:24 | 109 | ${tmeta(something) unknown_option} | ^^^^^^^^^^^^^^ error: expected curly braces --> ui/badmeta.rs:110:27 | 110 | ${if tmeta(something) unknown_option {}} | ^^^^^^^^^^^^^^ error: missing `as ...` in meta expansion --> ui/badmeta.rs:116:7 | 116 | ${tmeta(dot)} // forgot `.. as`, so `"."` not even parsed | ^^^^^^^^^ error: comparison of string/byte/character literals with suffixes is not supported (literal) --> ui/badmeta.rs:10:40 | 10 | #[deftly(integer = "42", with_suffix = "42"x)] | ^^^^^ ... 119 | / derive_deftly_adhoc! { 120 | | DataType: 121 | | ${if approx_equal("42", ${tmeta(with_suffix) as str}) {}} 122 | | } | |_- in this macro invocation | = note: this error originates in the macro `derive_deftly_driver_DataType` which comes from the expansion of the macro `derive_deftly_adhoc` (in Nightly builds, run with -Z macro-backtrace for more info) error: comparison of string/byte/character literals with suffixes is not supported (comparison) --> ui/badmeta.rs:121:10 | 121 | ${if approx_equal("42", ${tmeta(with_suffix) as str}) {}} | ^^^^^^^^^^^^ error: integer literal comparison with both values >u64 is not supported (left: number too large to fit in target type) --> ui/badmeta.rs:125:23 | 125 | ${if approx_equal(0x12345678_12345678_12345678_aaaaaaaa, | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error: integer literal comparison with both values >u64 is not supported (right: number too large to fit in target type) --> ui/badmeta.rs:11:19 | 11 | #[deftly(int128 = "0x12345678_12345678_12345678_12345678")] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ... 123 | / derive_deftly_adhoc! { 124 | | DataType: 125 | | ${if approx_equal(0x12345678_12345678_12345678_aaaaaaaa, 126 | | ${tmeta(int128) as token_stream}) {}} 127 | | } | |_- in this macro invocation | = note: this error originates in the macro `derive_deftly_driver_DataType` which comes from the expansion of the macro `derive_deftly_adhoc` (in Nightly builds, run with -Z macro-backtrace for more info) error: integer literal comparison with both values >u64 is not supported (comparison) --> ui/badmeta.rs:125:10 | 125 | ${if approx_equal(0x12345678_12345678_12345678_aaaaaaaa, | ^^^^^^^^^^^^ error: beta derive-deftly feature used, without `beta_deftly` template option --> ui/badmeta.rs:130:22 | 130 | ${fmeta(missing) default { 42 }} | ^^^^^^^ warning: unused macro definition: `derive_deftly_template_WrongRequestSyntax` --> ui/badmeta.rs:104:5 | 104 | WrongRequestSyntax: | ^^^^^^^^^^^^^^^^^^ | = note: `#[warn(unused_macros)]` on by default work/tests/ui/badoptions.rs0000664000000000000000000000225014763657303013234 0ustar // Tests involving driver and template options use derive_deftly::{define_derive_deftly, derive_deftly_adhoc, Deftly}; define_derive_deftly! { BadOptionsTemplate: broken template; } #[derive(Deftly)] #[derive_deftly(BadOptionsTemplate[dbg])] #[derive_deftly_adhoc] struct BadOptionsDriver; derive_deftly_adhoc! { BadOptionsDriver with unknown option: } derive_deftly_adhoc! { BadOptionsDriver for wombat: } derive_deftly_adhoc! { BadOptionsDriver for union: } derive_deftly_adhoc! { BadOptionsDriver for struct: } derive_deftly_adhoc! { BadOptionsDriver dbg, expect items: syntax error; } const K: () = derive_deftly_adhoc! { BadOptionsDriver expect expr: (*) }; const K2: () = derive_deftly_adhoc! { BadOptionsDriver expect expr: 1, }; derive_deftly_adhoc! { BadOptionsDriver dbg, for struct, for union: } define_derive_deftly! { #[allow(dead_code)] BadAttributeTemplate: } derive_deftly_adhoc! { BadOptionsDriver: ${define DEF {}} ${defcond COND true} $DEF $COND ${if COND {}} ${if DEF {}} } derive_deftly_adhoc! { BadOptionsDriver: ${define DEF $DEF} $DEF } fn main() {} work/tests/ui/badoptions.stderr0000664000000000000000000000742614763657303014125 0ustar error: expected one of `!` or `::`, found `template` --> ui/badoptions.rs:7:12 | 7 | broken template; | ^^^^^^^^ expected one of `!` or `::` ... 10 | #[derive(Deftly)] | ------ in this derive macro expansion | = note: this error originates in the macro `derive_deftly_template_BadOptionsTemplate` which comes from the expansion of the derive macro `Deftly` (in Nightly builds, run with -Z macro-backtrace for more info) error: unknown derive-deftly option --> ui/badoptions.rs:16:22 | 16 | BadOptionsDriver with unknown option: | ^^^^ error: unknown value for expected driver kind (in `for` option) --> ui/badoptions.rs:20:26 | 20 | BadOptionsDriver for wombat: | ^^^^^^ error: template defined for union, but applied to struct (expected kind) --> ui/badoptions.rs:24:26 | 24 | BadOptionsDriver for union: | ^^^^^ error: template defined for union, but applied to struct (actual kind) --> ui/badoptions.rs:11:1 | 11 | / #[derive_deftly(BadOptionsTemplate[dbg])] 12 | | #[derive_deftly_adhoc] 13 | | struct BadOptionsDriver; | |________________________^ ... 23 | / derive_deftly_adhoc! { 24 | | BadOptionsDriver for union: 25 | | } | |_- in this macro invocation | = note: this error originates in the macro `derive_deftly_driver_BadOptionsDriver` which comes from the expansion of the macro `derive_deftly_adhoc` (in Nightly builds, run with -Z macro-backtrace for more info) error: expected `!` --> ui/badoptions.rs:33:12 | 33 | syntax error; | ^^^^^ error: expected one of `!` or `::`, found `error` --> $OUT_DIR[derive-deftly-macros]/derive-deftly~expansions~/dd-f29bf7103472ec5bb186263b.rs | | syntax error; | ^^^^^ expected one of `!` or `::` error: unexpected end of input, expected an expression --> ui/badoptions.rs:38:5 | 38 | (*) | ^ error: expected expression, found `)` --> $OUT_DIR[derive-deftly-macros]/derive-deftly~expansions~/dd-2e1c4810a8f15c56049b9d53.rs | | (*) | ^ expected expression error: unexpected token --> ui/badoptions.rs:43:6 | 43 | 1, | ^ error: contradictory values for expected driver kind (in `for` option) (first) --> ui/badoptions.rs:47:31 | 47 | BadOptionsDriver dbg, for struct, for union: | ^^^^^^ error: contradictory values for expected driver kind (in `for` option) (second) --> ui/badoptions.rs:47:43 | 47 | BadOptionsDriver dbg, for struct, for union: | ^^^^^ error: only doc attributes are supported --> ui/badoptions.rs:51:7 | 51 | #[allow(dead_code)] | ^^^^^ error: user-defined expansion `COND` not found --> ui/badoptions.rs:62:6 | 62 | $COND | ^^^^ error: user-defined condition `DEF` not found --> ui/badoptions.rs:64:10 | 64 | ${if DEF {}} | ^^^ error: this user-defined expansion used as a condition (perhaps you meant ${defcond ?} --> ui/badoptions.rs:58:14 | 58 | ${define DEF {}} | ^^^ error: probably-recursive user-defined expansion/condition (more than 100 deep) --> ui/badoptions.rs:70:14 | 70 | ${define DEF $DEF} | ^^^ error: reference involved in too-deep expansion/condition, depth 1 --> ui/badoptions.rs:70:19 | 70 | ${define DEF $DEF} | ^^^ error: reference involved in too-deep expansion/condition, depth 0 --> ui/badoptions.rs:71:6 | 71 | $DEF | ^^^ error: include macro expected single expression in source --> $OUT_DIR[derive-deftly-macros]/derive-deftly~expansions~/dd-9bb48b2e4ab54bba44238094.rs | | 1, | ^ | = note: `#[deny(incomplete_include)]` on by default work/tests/ui/badpaste.rs0000664000000000000000000000152114763657303012655 0ustar use derive_deftly::{derive_deftly_adhoc, Deftly}; #[derive(Deftly)] #[derive_deftly_adhoc] #[deftly(something = "Box")] struct DataType { foo: u8, bar: Vec, } derive_deftly_adhoc! { DataType: ${for fields { ${paste $ttype _ $ftype} }} } derive_deftly_adhoc! { DataType: struct ${paste $tname _ 42}; } derive_deftly_adhoc! { DataType: struct ${paste $ttype ${tmeta(something) as ty}}; } derive_deftly_adhoc! { DataType: // This expands to "r#struct Broken { }" ${paste tdefkwed} Broken { } } derive_deftly_adhoc! { DataType: // This attribute isn't actually provided, but that doesn't matter // for this test since it fails before even looking for it. ${define VIA_DEFINE ${tmeta(t_frag) as ident}} struct $; } fn main() {} work/tests/ui/badpaste.stderr0000664000000000000000000000257514763657303013546 0ustar error: multiple nontrivial entries in ${paste ...} --> ui/badpaste.rs:15:18 | 15 | ${paste $ttype _ $ftype} | ^^^^^ error: multiple nontrivial entries in ${paste ...} --> ui/badpaste.rs:15:27 | 15 | ${paste $ttype _ $ftype} | ^^^^^ error: not allowed in within ${paste ...} (or case_changing) --> ui/badpaste.rs:22:29 | 22 | struct ${paste $tname _ 42}; | ^^ error: multiple nontrivial entries in ${paste ...} --> ui/badpaste.rs:28:21 | 28 | struct ${paste $ttype ${tmeta(something) as ty}}; | ^^^^^ error: multiple nontrivial entries in ${paste ...} --> ui/badpaste.rs:28:29 | 28 | struct ${paste $ttype ${tmeta(something) as ty}}; | ^^^^^^^^^^^^^^^ error: expected one of `!` or `::`, found `Broken` --> ui/badpaste.rs:35:23 | 35 | ${paste tdefkwed} Broken { } | ^^^^^^ expected one of `!` or `::` error: not allowed in within ${paste ...} (or case_changing) --> ui/badpaste.rs:45:25 | 45 | struct $; | ^^^^^^^^^^ error: user-defined expansion is not pasteable because it isn't, itself, ${paste } --> ui/badpaste.rs:43:25 | 43 | ${define VIA_DEFINE ${tmeta(t_frag) as ident}} | ^ work/tests/ui/badreserved.rs0000664000000000000000000000157314763657303013367 0ustar // Tests of syntax we may wish to use for things in the future use derive_deftly::{define_derive_deftly, derive_deftly_adhoc, Deftly}; #[derive(Deftly)] #[derive_deftly_adhoc] struct ReserveDriver; derive_deftly_adhoc! { ReserveDriver: $[ future ] } derive_deftly_adhoc! { ReserveDriver: $ ${paste ident:upper} } derive_deftly_adhoc! { ReserveDriver: $r#"template content?"# } derive_deftly_adhoc! { ReserveDriver: mod prevent { //! template doc comment? } } derive_deftly_adhoc! { ReserveDriver: $/// template doc comment? mod prevent { } } derive_deftly_adhoc! { ReserveDriver: ${define 0 { mod x {} }} $0 } define_derive_deftly! { lowercase: } define_derive_deftly! { __lowercase: } define_derive_deftly! { _0unused: } define_derive_deftly! { _Unused: } fn main() {} work/tests/ui/badreserved.stderr0000664000000000000000000000351414763657303014243 0ustar error: expected one of: `$`, parentheses, curly braces, identifier, `<` --> ui/badreserved.rs:11:6 | 11 | $[ future ] | ^ error: not allowed in within ${paste ...} (or case_changing) --> ui/badreserved.rs:16:12 | 16 | $ | ^ error: not allowed in within ${paste ...} (or case_changing) --> ui/badreserved.rs:17:18 | 17 | ${paste ident:upper} | ^ error: expected one of: `$`, parentheses, curly braces, identifier, `<` --> ui/badreserved.rs:22:6 | 22 | $r#"template content?"# | ^^^^^^^^^^^^^^^^^^^^^^ error: inner attributes are reserved syntax, anywhere in derive-deftly templates --> ui/badreserved.rs:29:9 | 29 | //! template doc comment? | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: expected one of: `$`, parentheses, curly braces, identifier, `<` --> ui/badreserved.rs:36:6 | 36 | $/// template doc comment? | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: expected ident --> ui/badreserved.rs:44:14 | 44 | ${define 0 { mod x {} }} | ^ error: expected one of: `$`, parentheses, curly braces, identifier, `<` --> ui/badreserved.rs:45:6 | 45 | $0 | ^ error: template name may not start with a lowercase letter (after any underscores) --> ui/badreserved.rs:49:5 | 49 | lowercase: | ^^^^^^^^^ error: template name may not start with a lowercase letter (after any underscores) --> ui/badreserved.rs:53:5 | 53 | __lowercase: | ^^^^^^^^^^^ warning: unused macro definition: `derive_deftly_template__0unused` --> ui/badreserved.rs:57:5 | 57 | _0unused: | ^^^^^^^^ | = note: `#[warn(unused_macros)]` on by default warning: unused macro definition: `derive_deftly_template__Unused` --> ui/badreserved.rs:61:5 | 61 | _Unused: | ^^^^^^^ work/tests/ui/badselect1.rs0000664000000000000000000000176614763657303013114 0ustar // Here's a simple syntax for the Hash attribute. I'm using it to // imagine what parameters look like, and how a "skip" attribute might // look, and how a "hash_with" attrbite might look. //#![feature(trace_macros)] //trace_macros!(true); use derive_deftly::{derive_deftly_adhoc, Deftly}; use std::fmt::Debug; #[derive(Deftly, Default, Debug)] #[derive_deftly_adhoc] struct Both { #[deftly(left)] a: usize, #[deftly(right)] b: usize, #[deftly(right)] #[deftly(left)] c: usize, } derive_deftly_adhoc! { Both: #[derive(Default, Debug)] struct Left { $( ${select1 fmeta(left) { $fname: $ftype, } fmeta(right) { } } ) } #[derive(Default, Debug)] struct Right { $( ${select1 fmeta(sinister) { } fmeta(right) { $fname: $ftype, } } ) } } fn main() { assert_eq!(format!("{:?}", &Left::default()), "Left { a: 0 }",) } work/tests/ui/badselect1.stderr0000664000000000000000000000464014763657303013765 0ustar error: multiple conditions matched (in this field) --> ui/badselect1.rs:19:5 | 19 | / #[deftly(right)] 20 | | #[deftly(left)] 21 | | c: usize, | |____________^ ... 24 | / derive_deftly_adhoc! { 25 | | Both: 26 | | 27 | | #[derive(Default, Debug)] ... | 45 | | } | |_- in this macro invocation | = note: this error originates in the macro `derive_deftly_driver_Both` which comes from the expansion of the macro `derive_deftly_adhoc` (in Nightly builds, run with -Z macro-backtrace for more info) error: multiple conditions matched (true condition) --> ui/badselect1.rs:31:15 | 31 | fmeta(left) { $fname: $ftype, } | ^^^^^ error: multiple conditions matched (true condition) --> ui/badselect1.rs:32:15 | 32 | fmeta(right) { } | ^^^^^ error: no conditions matched, and no else clause (in this field) --> ui/badselect1.rs:15:5 | 15 | / #[deftly(left)] 16 | | a: usize, | |____________^ ... 24 | / derive_deftly_adhoc! { 25 | | Both: 26 | | 27 | | #[derive(Default, Debug)] ... | 45 | | } | |_- in this macro invocation | = note: this error originates in the macro `derive_deftly_driver_Both` which comes from the expansion of the macro `derive_deftly_adhoc` (in Nightly builds, run with -Z macro-backtrace for more info) error: no conditions matched, and no else clause (select1 expansion) --> ui/badselect1.rs:39:15 | 39 | ${select1 | ^^^^^^^ error[E0433]: failed to resolve: use of undeclared type `Left` --> ui/badselect1.rs:48:33 | 48 | assert_eq!(format!("{:?}", &Left::default()), "Left { a: 0 }",) | ^^^^ use of undeclared type `Left` | help: there is an enum variant `core::fmt::Alignment::Left` and 4 others; try using the variant's enum | 48 | assert_eq!(format!("{:?}", &core::fmt::Alignment::default()), "Left { a: 0 }",) | ~~~~~~~~~~~~~~~~~~~~ 48 | assert_eq!(format!("{:?}", &itertools::Either::default()), "Left { a: 0 }",) | ~~~~~~~~~~~~~~~~~ 48 | assert_eq!(format!("{:?}", &itertools::EitherOrBoth::default()), "Left { a: 0 }",) | ~~~~~~~~~~~~~~~~~~~~~~~ 48 | assert_eq!(format!("{:?}", &std::fmt::Alignment::default()), "Left { a: 0 }",) | ~~~~~~~~~~~~~~~~~~~ work/tests/ui/badsemver.rs0000664000000000000000000000047214763657303013046 0ustar use derive_deftly::template_export_semver_check; template_export_semver_check!("0.2"); template_export_semver_check!("0.2.3"); template_export_semver_check!("1000000000.0"); template_export_semver_check!(garbage); template_export_semver_check!("garbage"); template_export_semver_check!("1.2.3.4"); fn main() {} work/tests/ui/badsemver.stderr0000664000000000000000000000236314763657303013726 0ustar error: derive-deftly has been updated! Please check the changelog for breaking changes since 0.2.x Search for section titles containing "template export semver". --> ui/badsemver.rs:3:31 | 3 | template_export_semver_check!("0.2"); | ^^^^^ error: derive-deftly has been updated! Please check the changelog for breaking changes since 0.2.x Search for section titles containing "template export semver". --> ui/badsemver.rs:4:31 | 4 | template_export_semver_check!("0.2.3"); | ^^^^^^^ error: declared version is greater than this version of derive-deftly! --> ui/badsemver.rs:5:31 | 5 | template_export_semver_check!("1000000000.0"); | ^^^^^^^^^^^^^^ error: expected string literal --> ui/badsemver.rs:7:31 | 7 | template_export_semver_check!(garbage); | ^^^^^^^ error: bad number: invalid digit found in string --> ui/badsemver.rs:8:31 | 8 | template_export_semver_check!("garbage"); | ^^^^^^^^^ error: too many components in version number --> ui/badsemver.rs:9:31 | 9 | template_export_semver_check!("1.2.3.4"); | ^^^^^^^^^ work/tests/ui/dbgdump.rs0000664000000000000000000000246014763657303012517 0ustar use derive_deftly::{define_derive_deftly, derive_deftly_adhoc, Deftly}; define_derive_deftly! { BasicDbgs: ${dbg { fn $<$tdefkwd _fn>() {} }} ${dbg "noted" { fn $<$tdefkwd _noted>() {} }} ${if dbg(is_struct) {}} ${if dbg("noted", is_enum) {}} // errors ${dbg { $fname }} ${dbg "error" { $fname }} ${if dbg(is_empty($fname)) {}} ${if dbg("error", is_empty($fname)) {}} } define_derive_deftly! { Contexts: ${ignore $( ${dbg { $vname }} ) $( ${dbg "noted vtype" { $vtype }} ) $( ${dbg "noted fname" { $fname }} ) } } define_derive_deftly! { Pastes: fn $<${dbg "paste" { "pasting_" $ttype "_pasted" }}>() {} $<${dbg "paste error" { "x" $fname "y" }}> } #[derive(Deftly)] #[derive_deftly(BasicDbgs)] #[derive_deftly_adhoc] struct DataType; #[derive(Deftly)] #[derive_deftly(Contexts)] struct Tuple(i32); #[derive(Deftly)] #[derive_deftly(Contexts)] struct Struct { f: i32, } #[derive(Deftly)] #[derive_deftly(Contexts)] enum Enum { Tuple(i32), Struct { f: i32 }, } #[derive(Deftly)] #[derive_deftly(Pastes)] struct WithGenerics(T); derive_deftly_adhoc! { DataType: ${dbg { fn $<$tdefkwd _adhoc>() {} }} ${dbg "adhoc" { fn $() {} }} ${if dbg("adhoc", v_is_unit) {} } } fn main() {} work/tests/ui/dbgdump.stderr0000664000000000000000000000602414763657303013376 0ustar error: must be within a field (so, in a repeat group) --> ui/dbgdump.rs:13:14 | 13 | ${dbg { $fname }} | ^^^^^ ... 37 | #[derive(Deftly)] | ------ in this derive macro expansion | = note: this error originates in the macro `derive_deftly_template_BasicDbgs` which comes from the expansion of the derive macro `Deftly` (in Nightly builds, run with -Z macro-backtrace for more info) error: must be within a field (so, in a repeat group) --> ui/dbgdump.rs:14:22 | 14 | ${dbg "error" { $fname }} | ^^^^^ ... 37 | #[derive(Deftly)] | ------ in this derive macro expansion | = note: this error originates in the macro `derive_deftly_template_BasicDbgs` which comes from the expansion of the derive macro `Deftly` (in Nightly builds, run with -Z macro-backtrace for more info) error: must be within a field (so, in a repeat group) --> ui/dbgdump.rs:15:24 | 15 | ${if dbg(is_empty($fname)) {}} | ^^^^^ ... 37 | #[derive(Deftly)] | ------ in this derive macro expansion | = note: this error originates in the macro `derive_deftly_template_BasicDbgs` which comes from the expansion of the derive macro `Deftly` (in Nightly builds, run with -Z macro-backtrace for more info) error: must be within a field (so, in a repeat group) --> ui/dbgdump.rs:16:33 | 16 | ${if dbg("error", is_empty($fname)) {}} | ^^^^^ ... 37 | #[derive(Deftly)] | ------ in this derive macro expansion | = note: this error originates in the macro `derive_deftly_template_BasicDbgs` which comes from the expansion of the derive macro `Deftly` (in Nightly builds, run with -Z macro-backtrace for more info) error: expansion only valid in enums --> ui/dbgdump.rs:23:21 | 23 | $( ${dbg { $vname }} ) | ^^^^^ ... 42 | #[derive(Deftly)] | ------ in this derive macro expansion | = note: this error originates in the macro `derive_deftly_template_Contexts` which comes from the expansion of the derive macro `Deftly` (in Nightly builds, run with -Z macro-backtrace for more info) error: expansion only valid in enums --> ui/dbgdump.rs:23:21 | 23 | $( ${dbg { $vname }} ) | ^^^^^ ... 45 | #[derive(Deftly)] | ------ in this derive macro expansion | = note: this error originates in the macro `derive_deftly_template_Contexts` which comes from the expansion of the derive macro `Deftly` (in Nightly builds, run with -Z macro-backtrace for more info) error: must be within a field (so, in a repeat group) --> ui/dbgdump.rs:34:34 | 34 | $<${dbg "paste error" { "x" $fname "y" }}> | ^^^^^ ... 57 | #[derive(Deftly)] | ------ in this derive macro expansion | = note: this error originates in the macro `derive_deftly_template_Pastes` which comes from the expansion of the derive macro `Deftly` (in Nightly builds, run with -Z macro-backtrace for more info) work/tests/ui/explerror.rs0000664000000000000000000000033314763657303013114 0ustar use derive_deftly::{derive_deftly_adhoc, Deftly}; #[derive(Deftly)] #[derive_deftly_adhoc] struct Struct { field: usize, } derive_deftly_adhoc! { Struct: ${for fields { ${error "basic"}} } } fn main() {} work/tests/ui/explerror.stderr0000664000000000000000000000113314763657303013772 0ustar error: basic (in this field) --> ui/explerror.rs:6:5 | 6 | field: usize, | ^^^^^^^^^^^^ ... 9 | / derive_deftly_adhoc! { 10 | | Struct: 11 | | ${for fields { ${error "basic"}} } 12 | | } | |_- in this macro invocation | = note: this error originates in the macro `derive_deftly_driver_Struct` which comes from the expansion of the macro `derive_deftly_adhoc` (in Nightly builds, run with -Z macro-backtrace for more info) error: basic (template) --> ui/explerror.rs:11:28 | 11 | ${for fields { ${error "basic"}} } | ^^^^^^^ work/tests/ui/misplacedattr.rs0000664000000000000000000000172614763657303013735 0ustar use derive_deftly::Deftly; #[derive(Deftly)] struct Struct { #[derive_deftly(Lemons)] field: usize, } #[derive(Deftly)] struct Tuple( #[derive_deftly(Limes)] // usize, ); #[derive(Deftly)] union Union { #[derive_deftly(Oranges)] variant: usize, } #[derive(Deftly)] enum Apples { #[derive_deftly(Apples)] Apples { #[derive_deftly(OnlyThePreviousMisplacedAttributeIsReported)] apples: String, }, } #[derive(Deftly)] enum Pears { Pears { #[derive_deftly(Pears)] pears: String, }, } #[derive(Deftly)] enum Quince { Quince( #[derive_deftly(Quince)] // String, ), } struct Const; #[derive(Deftly)] struct Ludicrous { field: Const< { // accepted due to https://github.com/rust-lang/rust/issues/119116 #[derive_deftly(Silly)] #[deftly(foolish)] mod inner {} 42 }, >, } fn main() {} work/tests/ui/misplacedattr.stderr0000664000000000000000000000202614763657303014606 0ustar error: attribute is only meaningful at the data structure toplevel --> ui/misplacedattr.rs:5:5 | 5 | #[derive_deftly(Lemons)] | ^^^^^^^^^^^^^^^^^^^^^^^^ error: attribute is only meaningful at the data structure toplevel --> ui/misplacedattr.rs:11:5 | 11 | #[derive_deftly(Limes)] // | ^^^^^^^^^^^^^^^^^^^^^^^ error: attribute is only meaningful at the data structure toplevel --> ui/misplacedattr.rs:17:5 | 17 | #[derive_deftly(Oranges)] | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: attribute is only meaningful at the data structure toplevel --> ui/misplacedattr.rs:23:5 | 23 | #[derive_deftly(Apples)] | ^^^^^^^^^^^^^^^^^^^^^^^^ error: attribute is only meaningful at the data structure toplevel --> ui/misplacedattr.rs:33:9 | 33 | #[derive_deftly(Pears)] | ^^^^^^^^^^^^^^^^^^^^^^^ error: attribute is only meaningful at the data structure toplevel --> ui/misplacedattr.rs:41:9 | 41 | #[derive_deftly(Quince)] // | ^^^^^^^^^^^^^^^^^^^^^^^^ work/tests/ui/unkmeta.rs0000664000000000000000000000637114763657303012546 0ustar use derive_deftly::{define_derive_deftly, Deftly}; define_derive_deftly! { UseSomeAttrs: ${for fields { ${ignore { ${if tmeta(tnest(by_value)) { ${tmeta(tnest(by_value)) as str} }} ${if vmeta(vnest(by_value)) { ${vmeta(vnest(by_value)) as str} }} ${if fmeta(fnest(by_value)) { ${fmeta(fnest(by_value)) as str} }} ${if fmeta(vfnest(by_value)) { ${fmeta(vfnest(by_value)) as str} }} ${if vmeta(vfnest(by_value)) { ${vmeta(vfnest(by_value)) as str} }} ${if tmeta(tnest(bool_only)) {}}, ${if vmeta(vnest(bool_only)) {}}, ${if fmeta(fnest(bool_only)) {}}, }}}} } #[derive(Deftly)] #[derive_deftly(UseSomeAttrs)] #[deftly(unknown)] // ERROR (simply not recognised) #[deftly(tnest)] // ERROR (is only a container) #[deftly(tnest = '1')] // (error, but suppressed) #[deftly(tnest())] // ok #[deftly(tnest(bool_only = 42))] // ERROR (only recognised as boolean) #[deftly(tnest(bool_only(deeper)))] // ERROR (too deep, unrecog.. subpath) #[deftly(tnest(by_value(deeper)))] // ERROR (too deep, wanted value instead) #[deftly(tnest(bool_only))] // ok #[deftly(vnest(bool_only))] // ok #[deftly(fnest(bool_only))] // ERROR (wrong scope) #[deftly(tnest(by_value = "t"))] // ok #[deftly(vnest(by_value = "t"))] // ok #[deftly(fnest(by_value = "t"))] // ERROR (wrong scope) #[deftly(vfnest(by_value = "t"))] // ok (struct counts as V) struct StructTop { field: usize, // } #[derive(Deftly)] #[derive_deftly(UseSomeAttrs)] struct StructField { #[deftly(tnest(bool_only))] // ERROR (wrong scope) #[deftly(vnest(bool_only))] // ERROR (wrong scope) #[deftly(fnest(bool_only))] // ok #[deftly(fnest = '1')] // ERROR (is only a container) #[deftly(tnest(by_value = "f"))] // ERROR (wrong scope) #[deftly(vnest(by_value = "f"))] // ERROR (wrong scope) #[deftly(fnest(by_value = "f"))] // ok field: usize, } #[derive(Deftly)] #[derive_deftly(UseSomeAttrs)] #[deftly(tnest(bool_only))] // ok #[deftly(vnest(bool_only))] // ERROR (wrong scope) #[deftly(fnest(bool_only))] // ERROR (wrong scope) #[deftly(tnest(by_value = "t"))] // ok #[deftly(vnest(by_value = "t"))] // ERROR (wrong scope) #[deftly(fnest(by_value = "t"))] // ERROR (wrong scope) #[deftly(vfnest(by_value = "t"))] // ERROR (should be per-field or -variant) enum EnumTop { #[deftly(fnest(bool_only))] // ERROR (wrong scope, duplicate) Variant { field: usize, // }, } #[derive(Deftly)] #[derive_deftly(UseSomeAttrs)] enum EnumVariant { #[deftly(tnest(bool_only))] // ERROR (wrong scope) #[deftly(vnest(bool_only))] // ok #[deftly(fnest(bool_only))] // ERROR (wrong scope) #[deftly(tnest(by_value = "v"))] // ERROR (wrong scope) #[deftly(vnest(by_value = "v"))] // ok #[deftly(fnest(by_value = "v"))] // ERROR (wrong scope) Variant { field: usize, // }, } #[derive(Deftly)] #[derive_deftly(UseSomeAttrs)] enum EnumField { Variant { #[deftly(tnest(bool_only))] // ERROR (wrong scope) #[deftly(vnest(bool_only))] // ERROR (wrong scope) #[deftly(fnest(bool_only))] // ok #[deftly(tnest(by_value = "f"))] // ERROR (wrong scope) #[deftly(vnest(by_value = "f"))] // ERROR (wrong scope) #[deftly(fnest(by_value = "f"))] // ok field: usize, }, } work/tests/ui/unkmeta.stderr0000664000000000000000000001311614763657303013420 0ustar error: meta attribute provided, but not recognised by template --> ui/unkmeta.rs:23:10 | 23 | #[deftly(unknown)] // ERROR (simply not recognised) | ^^^^^^^ error: meta attribute provided, but not recognised; template only uses it as a container --> ui/unkmeta.rs:24:10 | 24 | #[deftly(tnest)] // ERROR (is only a container) | ^^^^^ error: meta attribute value provided, but is used only as a boolean --> ui/unkmeta.rs:27:16 | 27 | #[deftly(tnest(bool_only = 42))] // ERROR (only recognised as boolean) | ^^^^^^^^^ error: meta attribute provided, but not recognised by template --> ui/unkmeta.rs:28:26 | 28 | #[deftly(tnest(bool_only(deeper)))] // ERROR (too deep, unrecog.. subpath) | ^^^^^^ error: nested meta provided and not recognised; but, template would recognise #[deftly(tnest(by_value = ..)) --> ui/unkmeta.rs:29:25 | 29 | #[deftly(tnest(by_value(deeper)))] // ERROR (too deep, wanted value instead) | ^^^^^^ error: meta attribute provided for struct, but recognised by template only for field (fmeta) --> ui/unkmeta.rs:32:16 | 32 | #[deftly(fnest(bool_only))] // ERROR (wrong scope) | ^^^^^^^^^ error: meta attribute provided for struct, but recognised by template only for field (fmeta) --> ui/unkmeta.rs:35:16 | 35 | #[deftly(fnest(by_value = "t"))] // ERROR (wrong scope) | ^^^^^^^^ error: meta attribute provided for field, but recognised by template only for struct/enum (tmeta) --> ui/unkmeta.rs:44:20 | 44 | #[deftly(tnest(bool_only))] // ERROR (wrong scope) | ^^^^^^^^^ error: meta attribute provided for field, but recognised by template only for struct/variant (vmeta) --> ui/unkmeta.rs:45:20 | 45 | #[deftly(vnest(bool_only))] // ERROR (wrong scope) | ^^^^^^^^^ error: meta attribute provided, but not recognised; template only uses it as a container --> ui/unkmeta.rs:47:14 | 47 | #[deftly(fnest = '1')] // ERROR (is only a container) | ^^^^^ error: meta attribute provided for field, but recognised by template only for struct/enum (tmeta) --> ui/unkmeta.rs:48:20 | 48 | #[deftly(tnest(by_value = "f"))] // ERROR (wrong scope) | ^^^^^^^^ error: meta attribute provided for field, but recognised by template only for struct/variant (vmeta) --> ui/unkmeta.rs:49:20 | 49 | #[deftly(vnest(by_value = "f"))] // ERROR (wrong scope) | ^^^^^^^^ error: meta attribute provided for enum, but recognised by template only for struct/variant (vmeta) --> ui/unkmeta.rs:57:16 | 57 | #[deftly(vnest(bool_only))] // ERROR (wrong scope) | ^^^^^^^^^ error: meta attribute provided for enum, but recognised by template only for field (fmeta) --> ui/unkmeta.rs:58:16 | 58 | #[deftly(fnest(bool_only))] // ERROR (wrong scope) | ^^^^^^^^^ error: meta attribute provided for enum, but recognised by template only for struct/variant (vmeta) --> ui/unkmeta.rs:60:16 | 60 | #[deftly(vnest(by_value = "t"))] // ERROR (wrong scope) | ^^^^^^^^ error: meta attribute provided for enum, but recognised by template only for field (fmeta) --> ui/unkmeta.rs:61:16 | 61 | #[deftly(fnest(by_value = "t"))] // ERROR (wrong scope) | ^^^^^^^^ error: meta attribute provided for enum, but recognised by template only for struct/variant/field (vmeta/fmeta) --> ui/unkmeta.rs:62:17 | 62 | #[deftly(vfnest(by_value = "t"))] // ERROR (should be per-field or -variant) | ^^^^^^^^ error: meta attribute provided for variant, but recognised by template only for struct/enum (tmeta) --> ui/unkmeta.rs:73:20 | 73 | #[deftly(tnest(bool_only))] // ERROR (wrong scope) | ^^^^^^^^^ error: meta attribute provided for variant, but recognised by template only for field (fmeta) --> ui/unkmeta.rs:75:20 | 75 | #[deftly(fnest(bool_only))] // ERROR (wrong scope) | ^^^^^^^^^ error: meta attribute provided for variant, but recognised by template only for struct/enum (tmeta) --> ui/unkmeta.rs:76:20 | 76 | #[deftly(tnest(by_value = "v"))] // ERROR (wrong scope) | ^^^^^^^^ error: meta attribute provided for variant, but recognised by template only for field (fmeta) --> ui/unkmeta.rs:78:20 | 78 | #[deftly(fnest(by_value = "v"))] // ERROR (wrong scope) | ^^^^^^^^ error: meta attribute provided for field, but recognised by template only for struct/enum (tmeta) --> ui/unkmeta.rs:88:24 | 88 | #[deftly(tnest(bool_only))] // ERROR (wrong scope) | ^^^^^^^^^ error: meta attribute provided for field, but recognised by template only for struct/variant (vmeta) --> ui/unkmeta.rs:89:24 | 89 | #[deftly(vnest(bool_only))] // ERROR (wrong scope) | ^^^^^^^^^ error: meta attribute provided for field, but recognised by template only for struct/enum (tmeta) --> ui/unkmeta.rs:91:24 | 91 | #[deftly(tnest(by_value = "f"))] // ERROR (wrong scope) | ^^^^^^^^ error: meta attribute provided for field, but recognised by template only for struct/variant (vmeta) --> ui/unkmeta.rs:92:24 | 92 | #[deftly(vnest(by_value = "f"))] // ERROR (wrong scope) | ^^^^^^^^ error[E0601]: `main` function not found in crate `$CRATE` --> ui/unkmeta.rs:96:2 | 96 | } | ^ consider adding a `main` function to `$DIR/ui/unkmeta.rs` work/tests/ui/unkmeta2.rs0000664000000000000000000000161214763657303012621 0ustar use derive_deftly::{define_derive_deftly, Deftly}; define_derive_deftly! { ShortCircuit: $( ${if all(fmeta(value), fmeta(short_circuit)) { ${ignore ${fmeta(value) as str}} }} ) } define_derive_deftly! { Require: $( ${if fmeta(require) { ${ignore ${fmeta(value) as str}} }} ) } #[derive(Deftly)] #[derive_deftly(ShortCircuit, Require)] struct Data { none: (), #[deftly(value = "ignored")] // ERROR, tested only as bool by ShortCircuit not_enabled: (), #[deftly(require, value = "used")] // ok required_used: (), #[deftly(short_circuit, value = "used")] // ok short_circuit_used: (), #[deftly(short_circuit)] // ERROR, not reached in the all() short_circuit_uselessly_enabled: (), #[deftly(require, short_circuit, value = "used")] // ok used_both_ways: (), } fn main() {} work/tests/ui/unkmeta2.stderr0000664000000000000000000000102414763657303013475 0ustar error: meta attribute provided with value, and (conditionally) recognised with value; but in these particular circumstances only used as a boolean --> ui/unkmeta2.rs:28:14 | 28 | #[deftly(value = "ignored")] // ERROR, tested only as bool by ShortCircuit | ^^^^^ error: meta attribute provided, and (conditionally) recognised; but not used in these particular circumstances --> ui/unkmeta2.rs:37:14 | 37 | #[deftly(short_circuit)] // ERROR, not reached in the all() | ^^^^^^^^^^^^^