Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .claude/context/csharp-bindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>`/`FixedArray<T>` 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<TData>()` 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.
2 changes: 1 addition & 1 deletion cmake/Dependencies.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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 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
Expand Down
6 changes: 6 additions & 0 deletions docs/content/guide/maps.md
Original file line number Diff line number Diff line change
Expand Up @@ -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()`.
19 changes: 19 additions & 0 deletions tests/csharp/FamilySurfaceTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,25 @@ public unsafe void BlittableMirrorsCoverTheHotRecords()
() => normals.AsSpan<ulong>()); // 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<uint> { };
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<TData>() remains, with its runtime size gate).
Assert.DoesNotContain(
typeof(Vector<Formats.ADT.Chunks.McnrEntry>).GetMethods(),
m => m.Name == "AsSpan" && !m.IsGenericMethod);
}

[Fact]
public void BareBaseInstanceThrowsFromTheDispatchDefaultArm()
{
Expand Down
Loading