linksplatform / linksplatform/Data.Doublets

MergeUsages writes null targets and wrong sources: the two-argument Link<T> constructor is the params overload

Open Beginner friendly
#515 0 comments 0 reactions 0 assignees View on GitHub
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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.