Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning and to Conventional Commits.
0.2.0 - 2026-07-10
Added
Candor.define(..., source_location:)— the location every fabricated name reports, overriding the body's. A caller whose body is a proc it generated on the user's behalf can now point the wrapper at what the user actually wrote, rather than at the generator. It is also the only way to fabricate from a body carrying no location of its own — a curried proc, a symbol-to-proc, a C-defined method — which is the caller assuming responsibility for the honesty the gem otherwise derives. A#callobject is not one of them and nosource_location:rescues it: that refusal is about the kind. Asource_location:that is not a[String, Integer]pair, or whose line is oneevalwill not take, raisesArgumentErrorbefore the target is touched.Candor::Signature.source_location!— the location gate, public besidemethod_name!andparameters!.Signature.compilecalls it, so a consumer installing dispatch itself can no longer forge a locationevalrefuses, nor one it silently misreads. Its line must fiteval's: a Cint.
0.1.0 - 2026-07-10
Added
Candor.define(target, name, aliases:, via:, parameters:, body:)— fabricates a real method whose arity,parameterskinds andsource_locationare the body's. Bodies may be a block, aProc, aMethodor anUnboundMethod; a#callobject or any body with anilsource_location(a curried proc, a C-defined method) raisesTypeError. Withvia:every call routes to that interceptor, which reaches the body throughCandor.body_name; without it the wrapper forwards straight to the body. A wrong-arity call or an unknown keyword raisesArgumentErrorat the wrapper, before the interceptor or the body runs. An unpassed optional is dropped from the forwarded arguments, so the body applies its own default.Candor::Signature— the low-level compiler, public on its own: a parameter shape, a call-site name and asource_locationin, a dispatch lambda out. Renders off a parameter's kind, never its name, soend:,it,_1,**niland destructuring parameters all wrap at full fidelity. Both call-site names are validated against a strict method-name pattern, and a malformedparametersshape raisesArgumentErrorrather than aSyntaxErrorfrom insideeval— including a shape whose entries are each legal but whose combination Ruby's parser rejects, which is compiled before the target is mutated so a rejected fabrication cannot destroy the method it was replacing.- Dispatch allocates nothing for shapes up to
Candor::Signature::KEYWORD_BRANCH_LIMIT(2) optional keywords; beyond it, exactly one Hash per call. Past the limit a body's keyrest is that Hash, so a**kwin the signature costs only the capture and re-splat Ruby charges any wrapper. Exact from Ruby 3.4: on earlier Rubies the VM charges adefine_method-created method for arguments crossing into it, and both the wrapper and the body are ones. A call carrying no keyword allocates nothing on every supported Ruby. - Fabrication is thread-safe through one global
Monitor; call-time dispatch takes no lock and never needs one. Re-fabricating a name overwrites the wrapper and the body in place, so it is warning-free underruby -w, leaves no orphaned body, and a concurrent caller sees the old method or the new one, never aNoMethodError. A frozen target, an unbindableMethod/UnboundMethodbody, a name under the reservedCandor::BODY_PREFIX, an uncallablevia:and a malformedparameters:all raise before the target is touched. sig/ships RBS for the public API:Candor.define,Candor.body_name,BODY_PREFIX, andCandor::SignaturewithKINDSandKEYWORD_BRANCH_LIMIT. Everything else isprivate_constant, which RBS cannot express and therefore does not declare.