Repository navigation
Description
Summary
Writing the module cache fails when a generic function's body holds an ExprCallMacro whose macro belongs to the module being written. The CallMacro * serializer refuses it:
SERIALIZER_VERIFYF ( !(ptr->module == thisModule), "did not expect to find macro from the current module" );
(src/builtin/module_builtin_ast_serialize.cpp:2618, reached from SerializeVisitor::preVisit(ExprCallMacro*)).
A call macro is replaced by its expansion only when a body is inferred. A generic's own body is stored uninferred in Module.generics, so the ExprCallMacro survives into the cache write. The shape is any annotation that registers a call macro in the compiling module from a function annotation's apply (a variadic-style [annotation] def f(...) whose call sites f(...) are rewritten by a macro) and a generic in the same module that calls f(...). A non-generic caller passes, and a caller that spells the macro's expansion directly passes.
Combined with the second report (a failed cache write fails the compile), a cold run of such a program exits 1 with no output under the default cache.
Reproduction
rename_and_register.das:
options gen2
module rename_and_register public
require daslib/ast
require daslib/ast_boost
require daslib/templates_boost
// `name(x)` is rewritten to `name`impl(x)`; the macro is added to the module being compiled
class ForwardCallMacro : AstCallMacro {
target : string
def override visit(prog : ProgramPtr; mod : Module?; var expr : ExprCallMacro?) : ExpressionPtr {
return qmacro($c(target)($e(expr.arguments[0])))
}
}
// `[rename_and_register]` renames the function to `name`impl` and registers the call macro
// `name` in the compiling module, the shape a variadic-style annotation takes
[function_macro(name = "rename_and_register")]
class RenameAndRegister : AstFunctionAnnotation {
def override apply(var func : FunctionPtr; var group : ModuleGroup; var args : AnnotationArgumentList; var errors : das_string) : bool {
let c_name = string(func.name)
let target = "{c_name}`impl"
func.name := target
compiling_module() |> add_call_macro(make_call_macro(c_name, new ForwardCallMacro(target = target)))
return true
}
}generic_call_macro.das:
options gen2
require rename_and_register
[rename_and_register]
def twice(x : int) : int {
return x * 2
}
// a generic: its body is stored uninferred, with the ExprCallMacro still in it
def outer(a : auto) : int {
return twice(a)
}
[export]
def main {
print("{outer(5)}\n")
}Run from a directory with no .cache/daslang/module_cache yet:
$ daslang generic_call_macro.das
$ echo $?
1
$ daslang generic_call_macro.das
10
$ daslang -no-module-cache generic_call_macro.das
10
$ daslang -module-cache x.dascache generic_call_macro.das
[W] das: serialize: program write failed: did not expect to find macro from the current module
ser: wrote 1379779 bytes to 'x.dascache'
$ echo $?
1
$ daslang -module-cache x.dascache generic_call_macro.das
ser: reparsing in place 'generic_call_macro.das' (registers at compile time)
deser: partial - 1 module(s) reparsed in place, rest from cache
10
Control: with def outer(a : int) : int (not generic) the cold run prints 10 and exits 0 under both the default and an explicit cache.
Expected
The cold run prints 10 and exits 0, as the warm run and -no-module-cache do. A module that registers macros is reparsed in place on every read anyway, so its uninferred generic bodies have no reason to be written: the serializer could skip the macro reference for the current module (or write the generic body as unparsed source), or preVisit(ExprCallMacro*) could treat a same-module macro as "this module is not cacheable" without failing.
Actual
The write fails with did not expect to find macro from the current module; with the default (quiet) cache the process exits 1 and prints nothing.
Environment
- daScript commit c4e4906 (master),
daslang0.6.4, built with gcc 14.2.0 (Debian 14.2.0-19) - Debian GNU/Linux 13, Linux 6.12 x86_64
Activity
lookibed commentedon Nov 9, 2026
Re-checked by the owner from this issue's reproducer on daScript abf1d40 (0.6.5): fixed upstream (GaijinEntertainment/daScript 2131620 GaijinEntertainment#4472 dasLLVM, 1404d5a GaijinEntertainment#4480 optimizer, f386f92 GaijinEntertainment#4482 cache and tools). The c2das-re probes were re-pinned in the repin PRs (lookibed/c2das-re#142-GaijinEntertainment#147; ruling 38 addendum 4).
lookibed commentedon Nov 9, 2026
Fixed upstream: GaijinEntertainment#4447 is closed and the fix is in master
abf1d40e35(0.6.5). Verified by running this issue's repro on a build of that commit.