From ebc827ca390a97141e6f4a5ab22346ccfe208946 Mon Sep 17 00:00:00 2001 From: Sergey Shumakov Date: Sun, 23 Aug 2026 20:09:37 +0300 Subject: [PATCH 1/3] csharp: pin the AsSpan unmanaged-constraint rod - scalar-span misuse is now a compile error welder-csharp da87061 moves the non-generic Vector/FixedArray AsSpan() from an instance method to a `where T : unmanaged` extension: call sites are unchanged for scalar/enum elements, but the record- and nested-element calls that used to throw at runtime (the AlphaMaps report) no longer compile and never appear in IntelliSense. AsSpan() keeps its runtime size gate; AsDataSpan() stays the safe bulk spelling. Binary-breaking on the managed side (instance -> extension), so the next release wants a version bump note. Test: a scalar vector still spans zero-copy through the extension, and reflection confirms no non-generic instance AsSpan remains to bind misuse to. Guide: the maps page now states the compile-time gate and the AlphaMaps indexing idiom. dotnet test 36/36. Co-Authored-By: Claude Fable 5 --- cmake/Dependencies.cmake | 2 +- docs/content/guide/maps.md | 6 ++++++ tests/csharp/FamilySurfaceTests.cs | 19 +++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index 203866e..04b0296 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -154,7 +154,7 @@ FetchContent_Declare(welder # — welder core resolves those through name_of, no rod feature involved. FetchContent_Declare(welder_csharp GIT_REPOSITORY https://github.com/skarndev/welder-csharp.git - GIT_TAG 1eea11a4f0a8404db659ba97e2e75f9c01d1586b) + GIT_TAG da87061b72f97e75cd598c788b3b1a5d2ef11139) # --- stb_dxt (BLP DXT/BC compression; single public-domain header) --- # Pinned to the last commit that touched stb_dxt.h (2021-07-12); the URL_HASH diff --git a/docs/content/guide/maps.md b/docs/content/guide/maps.md index 8f835f2..ff42e3d 100644 --- a/docs/content/guide/maps.md +++ b/docs/content/guide/maps.md @@ -207,3 +207,9 @@ The same applies to any scalar vector (`Indices`, `Heights`, …) through the non-generic `AsSpan()`. Both spans are zero-copy views of C++ memory — valid until a size-changing operation or `Dispose`, and writable (edits land directly in the entity). + +`AsSpan()` only *exists* for scalar/enum elements (it is a +`where T : unmanaged` extension), so calling it on a record- or +nested-container-element vector is a **compile error**, not a runtime throw +— `chunk.AlphaMaps` is a vector of per-layer buffers, so index a layer +first: `chunk.AlphaMaps[i].AsSpan()`. diff --git a/tests/csharp/FamilySurfaceTests.cs b/tests/csharp/FamilySurfaceTests.cs index b40d265..0eac43f 100644 --- a/tests/csharp/FamilySurfaceTests.cs +++ b/tests/csharp/FamilySurfaceTests.cs @@ -119,6 +119,25 @@ public unsafe void BlittableMirrorsCoverTheHotRecords() () => normals.AsSpan()); // size gate holds } + [Fact] + public void ScalarSpanIsCompileTimeGated() + { + // AsSpan() is a `where T : unmanaged` EXTENSION now: a scalar vector + // still spans zero-copy, while a record/nested-element call (the old + // "requires scalar" runtime throw, e.g. chunk.AlphaMaps.AsSpan()) + // no longer compiles at all. + using var nums = new Vector { }; + nums.Add(3); nums.Add(5); + var span = nums.AsSpan(); + span[1] = 7; + Assert.Equal(7u, nums[1]); + // The non-generic instance method is gone — nothing for misuse to + // bind to (AsSpan() remains, with its runtime size gate). + Assert.DoesNotContain( + typeof(Vector).GetMethods(), + m => m.Name == "AsSpan" && !m.IsGenericMethod); + } + [Fact] public void BareBaseInstanceThrowsFromTheDispatchDefaultArm() { From d30569a2e5eeb488e3afc8fcdc16c07fb553f846 Mon Sep 17 00:00:00 2001 From: Sergey Shumakov Date: Sun, 23 Aug 2026 20:10:01 +0300 Subject: [PATCH 2/3] notes: AsSpan compile-time gate design Co-Authored-By: Claude Fable 5 --- .claude/context/csharp-bindings.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/.claude/context/csharp-bindings.md b/.claude/context/csharp-bindings.md index 9b4c775..2cc97c0 100644 --- a/.claude/context/csharp-bindings.md +++ b/.claude/context/csharp-bindings.md @@ -352,3 +352,19 @@ cmake --build build/csharp --target wowlib_cs files default to `net8.0`; with only the .NET 10 runtime installed a `net8.0` app fails to launch — target `net10.0` (or install the 8.0 runtime). + +## AsSpan is compile-time gated (welder-csharp PR #7 / wowlib PR #7) + +- Non-generic `AsSpan()` on `Vector`/`FixedArray` is a + `where T : unmanaged` EXTENSION (`WelderSpanExtensions`), not an instance + method — an instance method cannot constrain the class's own T. Record and + nested-container elements get a compile error (the old "requires scalar" + runtime throw is gone); the body survives as `internal ScalarSpan()` for + the indexer/`ToArray()` fast paths. +- `unmanaged` is exactly the bound scalar/enum set: every other bound element + type is a managed wrapper class. +- `AsSpan()` stays an instance method with its runtime size gate + (size equality is not expressible in the type system); `AsDataSpan()` is + the safe spelling. Instance-vs-extension overload resolution is safe: with + no type args the generic instance method is never applicable. +- Binary-breaking (instance -> extension): flag in the next release notes. From 7b6297e882f097c589bf008125430aed543153c9 Mon Sep 17 00:00:00 2001 From: Sergey Shumakov Date: Sun, 23 Aug 2026 20:12:37 +0300 Subject: [PATCH 3/3] deps: welder-csharp to the merged AsSpan-constraint main (49d1edd) Co-Authored-By: Claude Fable 5 --- cmake/Dependencies.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/Dependencies.cmake b/cmake/Dependencies.cmake index 04b0296..1c21cb7 100644 --- a/cmake/Dependencies.cmake +++ b/cmake/Dependencies.cmake @@ -154,7 +154,7 @@ FetchContent_Declare(welder # — welder core resolves those through name_of, no rod feature involved. FetchContent_Declare(welder_csharp GIT_REPOSITORY https://github.com/skarndev/welder-csharp.git - GIT_TAG da87061b72f97e75cd598c788b3b1a5d2ef11139) + GIT_TAG 49d1edd4389062fc354da25e31f68aae918ab74b) # --- stb_dxt (BLP DXT/BC compression; single public-domain header) --- # Pinned to the last commit that touched stb_dxt.h (2021-07-12); the URL_HASH