linksplatform / linksplatform/Data.Doublets
MergeUsages writes null targets and wrong sources: the two-argument Link<T> constructor is the params overload
- Dominant language
- C#
- Stars
- 14
- Forks
- 5
- PR merge metrics
- No merged PRs in 30d
Description
## Summary
`ILinksExtensions.MergeUsages` corrupts every link it re-points. Both substitutions it builds land in the wrong slots, so a merged usage ends up with a null target and a source taken from the wrong half of the doublet.
## Reproduction
Runnable, dependency-pinned reproduction (Platform.Data.Doublets 0.18.1, no decorators, so nothing but `MergeUsages` can touch the links):
```csharp
using var links = new UnitedMemoryLinks(databaseFilename);
var one = links.CreateAndUpdate(links.Constants.Null, links.Constants.Null); // merged away
var two = links.CreateAndUpdate(links.Constants.Null, links.Constants.Null); // survives
var three = links.CreateAndUpdate(links.Constants.Null, links.Constants.Null); // unrelated
var usageAsSource = links.CreateAndUpdate(one, three); // (4: 1 3)
var usageAsTarget = links.CreateAndUpdate(three, one); // (5: 3 1)
links.MergeUsages(one, two);
```
Actual output:
```
before: (1: 0 0) (2: 0 0) (3: 0 0) (4: 1 3) (5: 3 1)
after: (1: 0 0) (2: 0 0) (3: 0 0) (4: 3 0) (5: 2 0)
BUG usage as source: expected (4: 2 3), got (4: 3 0)
BUG usage as target: expected (5: 3 2), got (5: 2 0)
```
Re-pointing a usage must replace only the half that named `one`: `(4: 1 3)` should become `(4: 2 3)` and `(5: 3 1)` should become `(5: 3 2)`. Instead both links lose their target and get a source copied from the wrong place.
## Root cause
[`ILinksExtensions.cs#L1214`](https://github.com/linksplatform/Data.Doublets/blob/main/csharp/Platform.Data.Doublets/ILinksExtensions.cs#L1214) and [`#L1226`](https://github.com/linksplatform/Data.Doublets/blob/main/csharp/Platform.Data.Doublets/ILinksExtensions.cs#L1226):
```csharp
var substitution = new Link(newLinkIndex, links.GetTarget(usageAsSource));
...
var substitution = new Link(links.GetTarget(usageAsTarget), newLinkIndex);
```
Both calls bind to `public Link(params TLinkAddress[] values)` ([`Link.cs#L63`](https://github.com/linksplatform/Data.Doublets/blob/main/csharp/Platform.Data.Doublets/Link.cs#L63)) — there is no `(source, target)` constructor, only `(index, source, target)` and the `params` catch-all. `SetValues` reads a two-element list as `(index, source)` with a **default target** ([`Link.cs#L171-175`](https://github.com/linksplatform/Data.Doublets/blob/main/csharp/Platform.Data.Doublets/Link.cs#L171)):
```csharp
case 2:
index = values[0];
source = values[1];
target = default;
break;
```
So `new Link(2, 3)` is `(index: 2, source: 3, target: 0)`, not `(source: 2, target: 3)`. That explains `(4: 3 0)` exactly: source `3` is the intended target, target is `default`.
The second loop has a second, independent bug: for a usage-as-**target** the half that must survive is the **source**, but the code reads `links.GetTarget(usageAsTarget)` — the very address being merged away. Even with the constructor fixed it would write `(5: 1 2)` instead of `(5: 3 2)`.
## Suggested fix
```csharp
var index = links.GetIndex(usageAsSource);
var substitution = new Link(index, newLinkIndex, links.GetTarget(usageAsSource));
...
var index = links.GetIndex(usageAsTarget);
var substitution = new Link(index, links.GetSource(usageAsTarget), newLinkIndex);
```
It is worth grepping for other two-argument `new Link<...>(a, b)` call sites — the `params` overload makes this mistake silent at compile time.
## Notes
- `MergeAndDelete` calls `MergeUsages`, so it inherits the defect.
- The Rust port already documents this divergence: `doublets` 0.5.0 `src/decorators/mod.rs` notes "`MergeUsages` re-points sources and targets correctly; the C# version writes null targets because of a `params` constructor mix-up."
- Found while auditing C#/Rust parity for .
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in ILinksExtensions.cs at lines 1214 and 1226, then read Link.cs lines 63 and 171-175 to confirm how the params constructor maps values. Check the linked C# reproduction and search for other two-argument new Link(a, b) call sites. Done means MergeUsages preserves the unaffected source or target and the reproduction produces (4: 2 3) and (5: 3 2) without null targets.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- data
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 82/100