diff options
| -rw-r--r-- | docs/dev-docs/_sidebar.md | 3 | ||||
| -rw-r--r-- | docs/dev-docs/pages/issues/remove-r-print-macro.md | 87 | ||||
| -rw-r--r-- | docs/dev-docs/pages/templates/.name | 1 |
3 files changed, 90 insertions, 1 deletions
diff --git a/docs/dev-docs/_sidebar.md b/docs/dev-docs/_sidebar.md index acdb46f..dd735c4 100644 --- a/docs/dev-docs/_sidebar.md +++ b/docs/dev-docs/_sidebar.md @@ -3,7 +3,8 @@ * [AI Translation Rule](pages/abouts/ai-translation-rule) * [Markdown Code Verification System](pages/abouts/code-verify-system) * Issues + * [remove-r-print-macro](pages/issues/remove-r-print-macro) * [The Mod Pathfinder](pages/issues/the-mod-pathfinder) * [Some Situations Where You'd Be Like "Shit!"](pages/issues/the-shit-time) -* templates +* Templates * [Helpdoc Template](pages/templates/doc) diff --git a/docs/dev-docs/pages/issues/remove-r-print-macro.md b/docs/dev-docs/pages/issues/remove-r-print-macro.md new file mode 100644 index 0000000..e5ef4a6 --- /dev/null +++ b/docs/dev-docs/pages/issues/remove-r-print-macro.md @@ -0,0 +1,87 @@ +# Remove `r_print!` and `r_println!` Macros + +`r_print!` and `r_println!` are important macros in Mingling for use inside `#[help]` and `#[renderer]` functions, but their implementation is not clean: they implicitly introduce a `__renderer_inner_result` field. While this might look elegant at the API level, it is **incorrect** and even **objectionable**. + +## Why **Objectionable**? + +Because you can't define declarative macros with `macro_rules` that wrap them. + +This is because `r_println!` depends on the implicit variable `__renderer_inner_result` injected by the `#[renderer]` proc macro into the function body. However, when a `macro_rules` declarative macro expands, **its internal code is placed in the caller's context**, which does not contain `__renderer_inner_result` — that variable only exists within the direct scope of the function body processed by `#[renderer]`. + +Let's look at some code to see why: + +```rust +// Suppose you want to write a wrapper macro: +macro_rules! my_println { + ($($arg:tt)*) => { + // When expanded here, the context is the call site of my_println!, + // not the location where the renderer function's injected variables live. + // So __renderer_inner_result is NOT visible here! + r_println!("Custom: {}", format!($($arg)*)); + }; +} + +#[renderer] +fn render_something(_p: ResultSomething) { + // Although this function body has __renderer_inner_result injected, + // the code from my_println! does NOT expand "inside this function body" — + // macro_rules expansion is essentially text replacement. The replaced code + // lives at the line where my_println! is called, and any variables referenced + // inside that macro must resolve to identifiers accessible at the call site. + // __renderer_inner_result is not a public, path-accessible variable; + // it's a hygienic local variable generated by the `#[renderer]` macro, + // and external macros cannot directly access it by name. + my_println!("{}", box_val); // Compile error: cannot find __renderer_inner_result +} +``` + +## Deeper Issues + +I have to admit, this is an early design flaw. After re-examining the code, I found the problem goes beyond "can't be wrapped". + +This isn't just a "can't wrap" issue — it reflects that `r_println!`'s design fundamentally violates Rust's macro hygiene principles: + +- **Implicit dependency**: Users of the macro must know that a variable named `__renderer_inner_result` exists — but this variable is neither part of the public API nor explicitly documented anywhere. +- **Scope leakage**: Variables injected by a proc macro should be confined to the scope processed by that macro. But `r_println!` attempts to make that variable accessible across macro calls, which effectively breaks Rust's identifier hygiene. +- **Non-composable**: Any attempt to wrap `r_println!` will fail, because declarative macros cannot "pass through" access to implicit variables. Even using a proc macro to wrap it would encounter similar hygiene issues. + +## Desired New Syntax + +I've designed two alternative approaches and will choose based on actual needs. + +### Option 1: Explicit Return + +```rust +#[renderer] +fn render_something(prev: ResultSomething) -> RenderResult { + let mut result = RenderResult::new(); + result.println(prev.to_string()); + // or + write!(result, "{}", prev.to_string()); + + result // return here +} +``` + +Clear boundaries — the entire rendering process is confined within the function body decorated by `#[help]` or `#[renderer]`, without introducing extra out-of-scope dependencies. The trade-off is slightly more boilerplate compared to the original approach. + +### Option 2: Resource Injection + +```rust +#[renderer] +fn render_something(prev: ResultSomething, result: &mut ResRenderResult) { + result.println(prev.to_string()); + // or + write!(result, "{}", prev.to_string()); + + result // return here +} +``` + +More flexible, but blurs the boundary between logic functions like `#[chain]` and rendering functions like `#[help]`. + +### Preferred Direction + +I lean toward **Option 1 (Explicit Return)**. There's no need to turn `RenderResult` into `ResRenderResult` as a global resource. + +As for rendering in logic functions like `#[chain]`, that should be handled by a separate system — not discussed here. diff --git a/docs/dev-docs/pages/templates/.name b/docs/dev-docs/pages/templates/.name index e69de29..1e1408c 100644 --- a/docs/dev-docs/pages/templates/.name +++ b/docs/dev-docs/pages/templates/.name @@ -0,0 +1 @@ +Templates |
