add cppExternalDefault sentinel for C++ template defaults (#25805, #25806)#25808
add cppExternalDefault sentinel for C++ template defaults (#25805, #25806)#25808puffball1567 wants to merge 1 commit into
Conversation
|
CI failures look unrelated to this change:
These all look like transient nimble dep resolution issues — the failed This PR's touched surface is narrow: a sentinel template in Will mark ready for review once CI looks healthy. Side note: is there a contributor-friendly way to retrigger failed jobs on this repo (= I don't have admin rights), or is this typically maintainer-driven? Happy to follow whichever convention you prefer. cc @Araq @ringabout @metagn |
…5805, nim-lang#25806) C++ allows generic types and member traits to default a template parameter to an expression that has no Nim equivalent: ``sizeof(T)`` on an opaque template argument, or ``Class<T>::value`` from a SFINAE/trait helper. There is currently no way to express this on `importcpp` types, so bindings either drop the parameter (= losing the type) or fabricate a Nim-side default that diverges from the C++-side one. Introduce a sentinel template ``cppExternalDefault[T](): T`` (= in `lib/system.nim`, gated on the new `nimHasCppExternalDefault` symbol) that is only meaningful as a generic parameter default on `importcpp` types. Calling it directly is a compile-time error; placed as a default it tells the compiler to emit no template argument at the corresponding slot in the C++ instantiation, so the C++-side default takes effect. Implementation -------------- The sentinel is detected syntactically in `semGenericParamList` (= no `semConstExpr` is run, which would trip the `{.error.}`). The default expression is replaced with the integer literal ``0`` carrying a new persistent node flag ``nfFromCppExternalDefault``. Codegen (`importedCppObject` in `ccgtypes`) checks that flag on each generic argument and skips emitting the corresponding template arg, trimming the preceding ``,`` separator. This keeps the rest of generic instantiation, type validation, and proc generic deduction on their existing paths: the bound argument is a normal ``static int`` value, only carrying an extra node flag that codegen inspects. Adding the flag to `PersistentNodeFlags` is what makes it survive ``copyTree`` through default-argument substitution. Behavior -------- \`\`\`nim type Buf*[T; N: static int = cppExternalDefault[int]()] {.importcpp: \"Buf<'0, '1>\".} = object var x: Buf[float] # C++: Buf<float> (= N = C++ default) var y: Buf[float, 100] # C++: Buf<float, 100> (= N = 100) discard cppExternalDefault[int]() # compile error \`\`\` Fixes nim-lang#25805. Fixes nim-lang#25806. Tests ----- \`tests/cpp/tcppexternaldefault_*.nim\` cover the apostrophe pattern, multiple consecutive sentinel slots (= consecutive separator trim), the no-apostrophe importcpp form (= synthesized template arg list), proc generic deduction through a sentinel-default type, and the direct-call \`{.error.}\`. Existing \`tests/cpp/\` continues to pass.
c57e510 to
4f36aac
Compare
|
For context on cases where the overload-alias workaround doesn't reach (= trait/SFINAE-dependent defaults, member-type-dependent defaults, combinatorial explosion in STL-shaped APIs, variadic template defaults), see the discussion at #25805 (comment): #25805 (comment) |
Summary
Closes #25805 and #25806.
C++ allows generic types and member traits to default a template parameter to an expression that has no Nim equivalent:
sizeof(T)on an opaque template argument (= referenced in [C++] sizeof on opaque importcpp template parameter prevents using C++ template defaults like sizeof(T) #25805)Class<T>::valuefrom a SFINAE / trait helper (= referenced in [C++] Cannot express C++ template defaults like Class<T>::value (SFINAE / trait helper pattern) as Nim generic param defaults #25806)There is currently no way to express this on
importcpptypes, so bindings either drop the parameter (= losing the type) or fabricate a Nim-side default that diverges from the C++-side one. This PR adds a sentinel template that delegates the default back to the C++ side.Design
A new sentinel template
cppExternalDefault[T](): Tis added tolib/system.nim, gated on the newnimHasCppExternalDefaultsymbol. It carries an{.error.}annotation so calling it directly is a compile-time error — it has no meaningful runtime value, only a compile-time role as a sentinel.When used as a generic parameter default on an
importcpptype and the user omits the parameter at instantiation, the compiler omits the corresponding template argument in the emitted C++ instantiation, so the C++-side default expression takes effect.The user-visible API is exactly:
cppExternalDefault) usable only as a generic-param defaultnimHasCppExternalDefault) for compatibility-shimmed bindingsImplementation
The intervention is intentionally narrow — kept to detection at one sem stage and emission at one codegen stage:
semtypes.semGenericParamListdetects the sentinel call syntactically (=nkCallwhose callee iscppExternalDefault, possibly with bracket type args) beforesemConstExpris run. DirectsemConstExprwould trip the{.error.}. The default expression is replaced with the integer literal0carrying a new persistent node flagnfFromCppExternalDefault.ccgtypes.importedCppObjectchecksnfFromCppExternalDefaulton each generic argument's static value and skips emitting the corresponding template arg, trimming the preceding,separator. Both branches of the codegen path (= apostrophe pattern likeBuf<'0, '1>and the synthesized fallback for bareimportcppnames) are updated.PersistentNodeFlagsinastdef.nimis extended withnfFromCppExternalDefaultso the flag survivescopyTreethrough default-argument substitution. Without this the flag is dropped during sigmatch's default-fill path and the codegen check never fires.The rest of generic instantiation, type validation, and proc generic deduction stay on their existing paths: the bound argument is a normal
static int 0, only carrying an extra node flag that codegen inspects. No new type flag, no special branches inseminst,semtypinst,sigmatch.matches, ortypeallowed.Diff:
lib/system.nim— sentinel templatecompiler/condsyms.nim—nimHasCppExternalDefaultfeature symbolcompiler/astdef.nim—nfFromCppExternalDefaultnode flag (added toPersistentNodeFlags)compiler/ic/enum2nif.nim— serialize the new node flagcompiler/semtypes.nim— sentinel detection + literal substitution + flag setcompiler/ccgtypes.nim— skip flagged template arg in both emit pathsComparison to C++ semantics
template<typename T, int N = 1024/sizeof(T)+8>[T; N: static int = cppExternalDefault[int]()]1024/sizeof(T)+8applies (= e.g.Buf<float>→ N = 264)template<typename T> struct Outer { template<typename U = T> struct Inner; };T— Nim cannot reach it, sentinel delegatestemplate<typename T, typename Alloc = std::allocator<T>>AllocT— same situation as abovetemplate<typename T> struct S { static constexpr int v = traits<T>::value; };then[N = S<T>::v]NS<T>::valueis a SFINAE/trait helper; not expressible in Nimtemplate<int N>(no default)[N: static int]template<int N = 4>(= constant default)[N: static int = 4]or sentinelWhen the user provides an explicit value at instantiation (=
Buf[float, 100]), the explicit value wins as expected — the sentinel only fires when the parameter is omitted.Tests
tests/cpp/tcppexternaldefault_*.nim(5 files) cover:_basic.nim— happy path: default fires when omitted, explicit value wins when given_multi_sentinel.nim— multiple consecutive sentinel slots (= consecutive separator trim)_no_apostrophe.nim— bareimportcppname (= synthesized template arg list path)_proc_deduction.nim— proc generic deduction through a sentinel-default type (= no extra hooks needed inseminst/semtypinst)_sentinel_error.nim— direct call →{.error.}triggersLocal regression: full
tests/cpp/runs clean (= the only failure istasync_cpp.nim'scannot open file: jester, which also fails on unmodifieddeveldue to missing local nimble dep; unrelated to this change).Test plan
bin/testament --targets:cpp pat "tests/cpp/tcppexternaldefault*"— 9/9 PASS (= 5 files × megatest variants)bin/testament --targets:cpp cat cpp— same pass set as unmodifieddevel(only pre-existing env-gated failure)Drafted; ready for review on green CI.