From 291b4995568a0b216d0e587e4d4ac689435b8141 Mon Sep 17 00:00:00 2001 From: Nikolas Siccha Date: Mon, 2 Nov 2026 23:50:52 +0100 Subject: [PATCH] Leave a quoted callee that has no name alone in normalize_defsig A macro may interpolate an arbitrary value as the callee of a call in a method body, e.g. `$(QuoteNode(Offset(2.0)))(x)` where `Offset` is a callable struct. The call lowers to a `:call` whose callee is that `QuoteNode`, and `normalize_defsig` called `nameof` on its value. Only functions, types and modules have a `nameof`, so walking the body threw `MethodError: no method matching nameof(::Offset)` and Revise could not track the file (`Revise.SignatureExtractionError`), although `include` runs it fine. Call `nameof` only for a quoted function, type or module (the folded `const` callees JuliaInterpreter produces) and return any other quoted value as it is: it is not a name, so it is never a self-call. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01GvfBWGxnUzWjHVUwWFLjCc --- src/signatures.jl | 16 +++++++++++++--- test/signatures.jl | 31 +++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/src/signatures.jl b/src/signatures.jl index 56aaa1a..fa503ed 100644 --- a/src/signatures.jl +++ b/src/signatures.jl @@ -253,9 +253,19 @@ end # try to normalize `def` to `GlobalRef` representation function normalize_defsig(@nospecialize(def), mod::Module) if def isa QuoteNode - # A quoted Symbol (e.g. the callee of `:foo(x)`) is already a name; only - # other wrapped values (functions, types, modules) need `nameof`. - def = def.value isa Symbol ? def.value : nameof(def.value) + val = def.value + if val isa Symbol + # A quoted Symbol (e.g. the callee of `:foo(x)`) is already a name. + def = val + elseif val isa Union{Function, DataType, UnionAll, Module} + # The quoted values that have a name (e.g. a `const` callee that + # JuliaInterpreter folded into a `QuoteNode`). + def = nameof(val) + else + # Any other quoted value, e.g. an instance of a callable struct that a macro + # interpolated as the callee of a call, is not a name: leave it as it is. + return def + end end if def isa Symbol def = GlobalRef(mod, def) diff --git a/test/signatures.jl b/test/signatures.jl index ff33d3c..2c91692 100644 --- a/test/signatures.jl +++ b/test/signatures.jl @@ -626,6 +626,37 @@ end @test LoweredCodeUtils.identify_framemethod_calls(frame) isa Any # must not throw end +# A callable value that is neither a function, a type nor a module: it has no `nameof`. +module QuotedCallee +struct Offset + value::Float64 +end +(offset::Offset)(x) = x + offset.value +end + +@testset "normalize_defsig with a quoted callable value as callee" begin + # A macro may interpolate an arbitrary value as the callee of a call, + # `$(QuoteNode(Offset(2.0)))(x)`, which lowers to a `:call` whose callee is that + # `QuoteNode`. Only functions, types and modules have a `nameof`; any other quoted + # value is not a name and must be left as it is instead of throwing. + for val in (QuotedCallee.Offset(2.0), 1, "s", Union{Int,String}) + qn = QuoteNode(val) + @test LoweredCodeUtils.normalize_defsig(qn, QuotedCallee) === qn + end + for (val, name) in ((sum, :sum), (Int, :Int64), (Base, :Base)) + @test LoweredCodeUtils.normalize_defsig(QuoteNode(val), QuotedCallee) === GlobalRef(QuotedCallee, name) + end + + ex = :(apply_offset(x) = $(QuoteNode(QuotedCallee.Offset(2.0)))(x)) + methodinfos, selfcalls = LoweredCodeUtils.identify_framemethod_calls(Frame(QuotedCallee, ex)) + @test haskey(methodinfos, GlobalRef(QuotedCallee, :apply_offset)) + @test isempty(selfcalls) + signatures = MethodInfoKey[] + methoddefs!(signatures, Frame(QuotedCallee, ex)) + @test length(signatures) == 1 + @test Base.invokelatest(QuotedCallee.apply_offset, 3.0) == 5.0 +end + module FoldedCallees struct Callable end end