Macro System: Unresolved Hygiene Constraint
0 reputation · 17 Aug 2020, 05:24 UTC
0 reputation · 17 Aug 2020, 05:24 UTC
Crystal’s macro system lacks a well‑defined hygiene rule. Macros cannot reliably reference local variables defined outside the macro, leading to compile‑time errors when the same code is built in different contexts. The order of macro expansion is unspecified; different compiler versions may generate code in a different order, producing non‑deterministic output for identical source files. Runtime macro evaluation is not supported, preventing macros from adapting to environment variables or runtime state, which hampers reproducibility in CI/CD workflows. Shards’ optional dependency handling does not enforce compatibility with macro definitions, and this can cause subtle build failures in repeatable environments.
Given these uncertainties, what is the intended hygiene rule for macros in Crystal? Will future compiler releases enforce a deterministic expansion order? How can CI pipelines be made deterministic under the current macro system?
Crystal’s macros are hygienic in the sense that identifiers created inside a macro are scoped to the macro body and cannot clash with outer symbols unless they are explicitly passed in. The language spec does not enforce a deterministic order of macro expansion, so different compiler revisions may expand macros in a different sequence. To make CI pipelines deterministic under the current system, pin the exact Crystal compiler version, avoid relying on implicit environment data inside macros, and pass all required values explicitly to the macro as arguments.
--release, --no-debug, --no-color) yields identical binaries across machines.Record the exact output of crystal --version and use that version in your CI definition (e.g., crystal: 0.34.1 in Docker or crystal --version 0.34.1 in a script).
Never refer to local variables or functions outside the macro without passing them as macro arguments. If a macro needs a value from the surrounding scope, call it like macro my_macro(val) and invoke it with my_macro(my_var).
Run the compiler with a fixed set of flags: crystal build src/main.cr --release --no-debug --no-color. Store the flag set in a CI environment variable to ensure consistency.
After each CI run, compare the produced binary or intermediate files byte‑by‑byte with a cached reference. A simple checksum (e.g., sha256sum) can be used for quick comparison.
In your codebase, annotate macros with comments that list required arguments and any external data they depend on. This makes it easier for future contributors to maintain determinism.
Which Crystal compiler version are you currently using? Knowing the exact version will help confirm whether the deterministic behavior you observe aligns with the compiler’s documented behavior.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.