diff --git a/src/welder/annotations.hpp b/src/welder/annotations.hpp index 43d3e5d..8059932 100644 --- a/src/welder/annotations.hpp +++ b/src/welder/annotations.hpp @@ -284,6 +284,46 @@ struct no_reassign_spec { } }; +// --- family_surface: opt a welded base into rod-synthesized family surfaces -- + +/** The stored form of a `family_surface` mark: the languages a welded BASE + class opts its family into a rod-synthesized version-agnostic surface for. + + A *family* is two or more welded classes deriving one welded base — the + shape a versioned class template welded per instantiation makes. A rod that + implements family surfaces (the C# rod's dispatch members are the first) + may then hoist the member intersection the derived classes bind identically + onto the base, so base-typed code reads and writes data without naming a + concrete. Synthesizing members onto a base is too intrusive to infer from + structure alone, so it is strictly OPT-IN: a rod must synthesize nothing + for a base that does not carry this mark. + + The mark goes on the BASE class (the entity that receives the synthesized + surface), not on the derived classes. welder core only stores and queries + it (@ref welder::family_surface_for); what a surface consists of is each + honoring rod's contract — a rod without the feature ignores the mark. + + Usable bare (all languages) or called with languages to scope it, like + exclude_spec: + @code + [[=welder::mark::family_surface]] // every honoring rod + [[=welder::mark::family_surface(welder::lang::py)]] // one language only + @endcode + Repeated annotations union their languages. +*/ +struct family_surface_spec { + unsigned mask = 0; /**< The languages to synthesize for; `0` == all languages. */ + + /** Scope the family surface to specific languages. + @tparam Ls the language enum types (deduced). + @param ls the languages to synthesize the surface for. + @return a scoped family_surface_spec. */ + template + consteval family_surface_spec operator()(Ls... ls) const { + return family_surface_spec{lang_mask(ls...)}; + } +}; + // --- trust_bindable: vouch that a type is representable outside welder's view -- /** The stored form of a `trust_bindable` member mark. @@ -596,6 +636,7 @@ inline constexpr detail::include_spec include{}; /**< @see welder: inline constexpr detail::only_spec only{}; /**< @see welder::detail::only_spec — must be called with ≥ 1 language */ inline constexpr detail::no_reassign_spec no_reassign{}; /**< @see welder::detail::no_reassign_spec */ inline constexpr detail::trust_bindable_spec trust_bindable{}; /**< @see welder::detail::trust_bindable_spec */ +inline constexpr detail::family_surface_spec family_surface{}; /**< @see welder::detail::family_surface_spec */ } // namespace mark // --- getter / setter: method-backed properties ------------------------------- diff --git a/src/welder/reflect.hpp b/src/welder/reflect.hpp index be98bda..a2926ee 100644 --- a/src/welder/reflect.hpp +++ b/src/welder/reflect.hpp @@ -230,6 +230,28 @@ consteval bool trusted_for(std::meta::info member, lang L) { return false; } +/** Does welded base class @a type opt its family into a rod-synthesized + version-agnostic surface for language @a L (a `family_surface` mark)? + + Synthesizing members onto a base is strictly opt-in (see + detail::family_surface_spec): a rod implementing family surfaces must + synthesize nothing for a base that does not carry the mark. welder core + only stores and answers this query; the surface itself is the honoring + rod's contract. + @param type a reflection of the welded base class to test. + @param L the target language. + @return `true` iff a `family_surface` mark covers @a L (mask `0` covers + all languages). */ +consteval bool family_surface_for(std::meta::info type, lang L) { + for (auto a : std::meta::annotations_of_with_type( + type, ^^detail::family_surface_spec)) { + auto s{std::meta::extract(a)}; + if (s.mask == 0 || (s.mask & lang_bit(L)) != 0) + return true; + } + return false; +} + /** Is data member @a member bound **read-only** for @a L by a `no_reassign` mark? `no_reassign` forces the read-only binding on a mutable member (see diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dd89b38..66a1086 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -91,6 +91,20 @@ add_test(NAME compile.user_lang COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} --target welder_user_lang --config $) +# The family_surface TYPE mark (compile-only): a welded base's opt-in to +# rod-synthesized version-agnostic family surfaces. Locks storage + the +# family_surface_for query: bare = all languages (user-minted included), +# scoped = exactly the named ones, repeats union, unmarked = false — the +# opt-in default an honoring rod must respect by synthesizing nothing. +add_library(welder_family_surface_mark OBJECT EXCLUDE_FROM_ALL + core/family_surface_mark.cpp) +target_compile_features(welder_family_surface_mark PRIVATE cxx_std_26) +target_link_libraries(welder_family_surface_mark PRIVATE welder::headers + welder_warnings) +add_test(NAME compile.family_surface_mark + COMMAND ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} + --target welder_family_surface_mark --config $) + # Template ↔ annotation semantics (compile-only): annotations on a template # declaration are readable through every *instantiation* (primary / partial / # explicit precedence; member, weld/mark and parameter annotations included) — diff --git a/tests/core/family_surface_mark.cpp b/tests/core/family_surface_mark.cpp new file mode 100644 index 0000000..1b3df5f --- /dev/null +++ b/tests/core/family_surface_mark.cpp @@ -0,0 +1,59 @@ +// The family_surface mark (compile-only; must SUCCEED). +// +// Locks the storage + query contract of `[[=welder::mark::family_surface]]`: +// the TYPE mark a welded base carries to opt its family (two or more welded +// classes deriving it) into a rod-synthesized version-agnostic surface. +// welder core only stores and answers `family_surface_for`; the surface +// itself is the honoring rod's contract (the C# rod's dispatch members are +// the first). The semantics locked here: +// - bare mark -> covers every language, shipped and user-minted alike; +// - scoped mark -> covers exactly the named languages; +// - repeated marks union their languages; +// - an unmarked base answers false everywhere — the opt-in default a rod +// must respect by synthesizing nothing. +#include +#include // family_surface_for + +namespace { + +inline constexpr welder::lang ruby{welder::user_lang<0>}; + +struct [[=welder::weld]] PlainBase {}; + +struct +[[=welder::weld]] +[[=welder::mark::family_surface]] +BareBase {}; + +struct +[[=welder::weld]] +[[=welder::mark::family_surface(welder::lang::py)]] +PyBase {}; + +struct +[[=welder::weld]] +[[=welder::mark::family_surface(welder::lang::py)]] +[[=welder::mark::family_surface(ruby)]] +UnionBase {}; + +// Unmarked: the opt-in default — false for every language. +static_assert(!welder::family_surface_for(^^PlainBase, welder::lang::py)); +static_assert(!welder::family_surface_for(^^PlainBase, welder::lang::lua)); +static_assert(!welder::family_surface_for(^^PlainBase, ruby)); + +// Bare: every language, user-minted ones included. +static_assert(welder::family_surface_for(^^BareBase, welder::lang::py)); +static_assert(welder::family_surface_for(^^BareBase, welder::lang::lua)); +static_assert(welder::family_surface_for(^^BareBase, ruby)); + +// Scoped: exactly the named languages. +static_assert(welder::family_surface_for(^^PyBase, welder::lang::py)); +static_assert(!welder::family_surface_for(^^PyBase, welder::lang::lua)); +static_assert(!welder::family_surface_for(^^PyBase, ruby)); + +// Repeated marks union. +static_assert(welder::family_surface_for(^^UnionBase, welder::lang::py)); +static_assert(welder::family_surface_for(^^UnionBase, ruby)); +static_assert(!welder::family_surface_for(^^UnionBase, welder::lang::lua)); + +} // namespace