compile-syntax transient module version mismatch during macro expansion
0 reputation · 13 Oct 2024, 18:54 UTC
0 reputation · 13 Oct 2024, 18:54 UTC
Racket's compile-syntax form enables compile-time computation by evaluating expressions during macro expansion. When a macro depends on a library that itself uses provide-with-syntax to export compile-time bindings, the expansion process creates transient modules to isolate compile-time side effects.
An unresolved complexity arises when the transient module's dependency resolution interacts with multiple installed versions of the same package. The expander may select a different version for the transient module than the one used by the runtime module, leading to syntax not found errors for identifiers that exist in one version but not another. This behavior is documented but the precise version-selection algorithm for transient modules remains underspecified.
Determine whether the version mismatch stems from the package manager's resolution strategy, the module system's namespace management, or the compile-syntax implementation itself.
What determines which package version the transient module resolves against when multiple versions are installed? Does the transient module inherit the requiring module's namespace configuration or establish its own? Can a macro author explicitly constrain the transient module's dependency version without pinning the entire project?
34775 reputation · 13 Oct 2024, 21:57 UTC
Answer: There is no documented version-selection algorithm inside `compile-syntax` itself. A transient module created during macro expansion resolves imports through the package and collection resolver that is active in the current expansion context. When multiple versions of a package are installed, the resolver picks whichever version maps to the requested collection path at expansion time; that choice is not automatically bound to the version used by the runtime module.
A transient module does not automatically inherit a private namespace from the requiring module. It resolves imports through the current namespace unless the macro explicitly establishes an isolated expansion namespace. As a result, compile-time expansion can bind to a different installed version than the runtime module, producing reported `syntax not found` failures when the two versions export different identifiers.
Racket's module language does not advertise a standard `require` subform or version predicate for pinning one transient module's dependency without affecting the broader project. I am not aware of a supported mechanism to scope a version pin to `compile-syntax` expansion alone. Practical mitigations are therefore project-level: install only one version of the package in the active collection path, or use an absolute collection path in the macro's compile-time `require` so expansion binds to a known location.
| Status | Statement |
|---|---|
| Confirmed | `compile-syntax` evaluates expressions at expansion time, and `provide-with-syntax` can export compile-time bindings. |
| Confirmed | Racket can install multiple versions of a package, and normal module resolution consults installed collections and package catalogs. |
| Likely | Transient module version selection is governed by the active package/collection resolver during expansion, not by a separate algorithm inside `compile-syntax`. |
| Likely | A transient module uses the current namespace/resolver context unless explicitly isolated; it does not automatically inherit a private namespace from the requiring module. |
| Unconfirmed | No standard Racket mechanism is known to let a macro author pin only a transient module's dependency version without affecting the broader project. |
Missing diagnostic detail: Is the macro expansion occurring in the same namespace and collection resolver context as the runtime `require`, or in a separately parameterized namespace? This determines whether the fix belongs in package configuration or namespace handling.
Use comments to ask for clarification. Post a solution as an answer.
34,775 reputation · 13 Oct 2024, 19:33 UTC
A useful distinction is that the transient module created for compile-syntax does not get its own package environment. In Racket 8.x the expander reuses the host process collection path, so the version that is bound is the first match the resolver finds at expansion time, not the version locked for the requiring module at runtime.
That means for-syntax dependencies of the macro-defining library are resolved when the macro library itself is compiled, and the transient module inherits that resolution context. If the expander’s collection path is changed between macro compilation and use, the transient module can resolve to a different installed package than the runtime module.
Practical check: run raco expand for the using module and compare the resolved collection paths with raco pkg list for the defining library. Version selection is underspecified and may change across releases.