Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ce9d0cf
fix(postconditions)!: Compare the rank, lengths and lower bounds of t…
cwouyang Oct 10, 2026
73b5d24
fix(postconditions): Leave an array passed as T as it was when compar…
cwouyang Oct 10, 2026
b05f8b0
docs(postconditions): Say which pair the dimension check leaves equal
cwouyang Oct 10, 2026
3f25c8f
test(postconditions): Hold the place of the dimension check and the f…
cwouyang Oct 10, 2026
8238148
fix(postconditions)!: Compare arrays of pointers by address
cwouyang Oct 10, 2026
b822d98
fix(postconditions): Classify an array of arrays of function pointers…
cwouyang Oct 10, 2026
9abcb7d
fix(postconditions): State what the address comparison requires of an…
cwouyang Oct 10, 2026
fd5cf36
test(postconditions): Cover unmanaged function pointers, pointers to …
cwouyang Oct 10, 2026
74bf652
fix(postconditions)!: Compare an array passed as T as a whole
cwouyang Oct 10, 2026
7cddecd
fix(postconditions)!: Copy arrays and value types declared in System.…
cwouyang Oct 11, 2026
96095ad
docs(postconditions): Correct comments and remarks on arrays, pointer…
cwouyang Oct 11, 2026
ddfed04
test(postconditions): Cover arrays of equal instances as T and one ar…
cwouyang Oct 11, 2026
2ddf551
docs: Say how arrays and the value types of System.Collections.Frozen…
cwouyang Oct 11, 2026
0721bee
docs: Correct the limits stated for arrays passed as an interface and…
cwouyang Oct 11, 2026
89995d8
docs: State exactly which getters run below an array compared as a whole
cwouyang Oct 11, 2026
90a6161
docs(adr): Record how arrays and the value types of the frozen namesp…
cwouyang Oct 11, 2026
cabf017
docs(adr): Correct an attribution and cross-reference the exceptions …
cwouyang Oct 11, 2026
a15a500
docs(adr): Bring the limit on the two values of the call up to date w…
cwouyang Oct 11, 2026
cf28518
docs: Say what is read of a sequence and of a frozen dictionary below…
cwouyang Oct 11, 2026
dc4b945
docs: Say what enumerating a sequence still calls below an array comp…
cwouyang Oct 11, 2026
d819fa7
docs: State the limit for an enumerator of a frozen collection that a…
cwouyang Oct 11, 2026
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
133 changes: 96 additions & 37 deletions CHANGELOG.md

Large diffs are not rendered by default.

12 changes: 6 additions & 6 deletions README.md

Large diffs are not rendered by default.

64 changes: 64 additions & 0 deletions docs/adr/0007-reflection-field-comparison.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
- **Amended**: 2026-10-09 — at the top level a `null` is compared as a value, and a `string`, a delegate type and a nullable value type are compared as a whole, without the patterns (#52, [ADR-0022](0022-faithful-old-copies-and-robust-comparison.md)); see the third Amendment under Implementation Notes
- **Amended**: 2026-10-10 — at the top level, one instance of a shared type whose state can change, passed as `actual` and as `expected`, is not compared member by member: the call throws `InvalidOperationException`, and the patterns do not apply (#54, rule S of [ADR-0022](0022-faithful-old-copies-and-robust-comparison.md)); see the fourth Amendment under Implementation Notes
- **Amended**: 2026-10-10 — two `FrozenSet<T>` or `FrozenDictionary<TKey, TValue>` values of one runtime type that a member or element holds are compared element by element, not by reference, and one of the two types as `T` is compared as a whole, without the patterns (#49, [ADR-0022](0022-faithful-old-copies-and-robust-comparison.md)); see the fifth Amendment under Implementation Notes
- **Amended**: 2026-10-11 — an array type or `System.Array` as `T` is compared as a whole, without the patterns; two arrays are equal only when they have the same rank, and the same length and lower bound in every dimension; two arrays of pointers or of function pointers are compared by address; an array whose element type is declared in `System.Collections.Frozen` and a value of a value type declared there are no longer compared by reference (#57, [ADR-0022](0022-faithful-old-copies-and-robust-comparison.md)); see the sixth Amendment under Implementation Notes

---

Expand Down Expand Up @@ -524,6 +525,68 @@ Still true:

This amendment adds no decision. It is the record that this ADR was considered for #49.

### Amendment (2026-10-11): Arrays as T, the rank, lengths and lower bounds of two arrays, and the namespace System.Collections.Frozen (#57)

The decision is unchanged. [Issue #57](https://github.com/cwouyang/uContract.NET/issues/57) changes
how `EnsureAssignable<T>()` compares arrays, and which types `Old<T>()` shares because they are
declared in the namespace `System.Collections.Frozen`. There are four points for this ADR:

- When the declared `T` is an array type or `System.Array`, two values that are not `null` are
compared as a whole: element by element, each element as a member or an element is. No member of
`T` is compared, and the patterns do not apply and are not examined.
- Two arrays that the comparison meets as a pair are equal only when they have the same rank, and
the same length and lower bound in every dimension. That holds for two arrays that a member or an
element holds, and for the two values of an array type or `System.Array` as `T`. It does not hold
for two arrays that are passed as a `T` that is neither an array type nor `System.Array`, such as
`object` or an interface: there only the members that are visible through `T` are compared, and a
member that leads back to the two values counts as equal.
- Two arrays of pointers or of function pointers are compared by the addresses that they hold.
- `Old<T>()` no longer shares an array whose element type is declared in that namespace, or a value
of a value type that is declared there. So the comparison no longer decides two of them by
reference (R5).

The amendment of [ADR-0022](0022-faithful-old-copies-and-robust-comparison.md) for #57 records the
changes, with their reasons, the behaviour before and after, the measurements and the limits. It
qualifies these statements of this ADR, each quoted by its opening words:

- The amendment of 2026-10-07: "Values of different runtime types are unequal (R6)." R6 excepts two
sequences, which R7 compares element by element. Two arrays are now compared so only when they
have the same rank, and the same length and lower bound in every dimension. Two arrays of
different rank are unequal, and no element of them is read.
- The same amendment: "**Value types** are compared by `Equals`, then by their fields when `Equals`
says "unequal" (R3)." That now also holds for a value of a value type that is declared in the
namespace `System.Collections.Frozen`. Such a value was compared by reference before, so two
boxes of equal content were different.
- The same amendment: "**Shared types** are compared by reference (R5)." An array whose element type
is declared in that namespace and a value of a value type that is declared there are no longer
shared types.
- The fifth Amendment above: "Two instances of one runtime type that is another type declared in the
namespace `System.Collections.Frozen` (two arrays of `FrozenSet<T>`, two instances of a class of
the caller declared there) are still compared by reference." Superseded for two arrays of
`FrozenSet<T>`: they are compared element by element, as any two arrays are. Two values of a value
type that is declared there are not compared by reference either. The statement stands for two
instances of a class of the caller that is declared there.
- The third Amendment above: "When `T` is a `string`, a delegate type or a nullable value" type, two
values that are not `null` are compared as a whole. The fifth Amendment added a `FrozenSet<T>` and
a `FrozenDictionary<TKey, TValue>`. The same now holds when `T` is an array type or
`System.Array`.
- The Status line of 2026-10-10 for #49: "one of the two types as `T` is compared as a whole,
without the patterns". So is an array type or `System.Array` as `T`. The Revision History rows of
2026-10-09 and of 2026-10-10 for #49 name the kinds of `T` that are compared as a whole; the array
types and `System.Array` are added to them.
- The third Amendment: "Each still holds for every other `T` with two values that are not `null`."
"Every other `T`" no longer includes an array type or `System.Array`.

Still true:

- The fourth Amendment above: "There are now three cases." There are still three. The second case, a
`T` that is compared as a whole, now also covers an array type and `System.Array`.
- The third Amendment above, for a `T` that is compared by its members: the members of `T` are
compared one by one. That includes a `T` such as `object` or an interface whose two values are
arrays: such a call is not compared as a whole.

This amendment adds no decision. It is the record that this ADR was considered for #57.

---

## References
Expand All @@ -545,3 +608,4 @@ This amendment adds no decision. It is the record that this ADR was considered f
| 2026-10-09 | Amended | The top level compares `null` as a value and three kinds of `T` as a whole (#52, ADR-0022). Decision unchanged. See Implementation Notes > third Amendment. |
| 2026-10-10 | Amended | At the top level, one instance of a shared type whose state can change, passed as `actual` and as `expected`, is not compared: `InvalidOperationException` (#54, ADR-0022). Decision unchanged. See Implementation Notes > fourth Amendment. |
| 2026-10-10 | Amended | Two `FrozenSet<T>` or `FrozenDictionary<TKey, TValue>` values of one runtime type that a member or element holds are compared by element, not by reference; one of the two types as `T` is compared as a whole (#49, ADR-0022). Decision unchanged. See Implementation Notes > fifth Amendment. |
| 2026-10-11 | Amended | An array type or `System.Array` as `T` is compared as a whole; two arrays are equal only with the same rank, lengths and lower bounds; two arrays of pointers or of function pointers are compared by address; an array whose element type is declared in `System.Collections.Frozen`, and a value of a value type declared there, are no longer compared by reference (#57, ADR-0022). Decision unchanged. See Implementation Notes > sixth Amendment. |
29 changes: 29 additions & 0 deletions docs/adr/0012-dotnet-improvements-over-java.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
- **Amended**: 2026-10-09 — `EnsureAssignable<T>()` compares a `null` `actual` or `expected` as a value and never rejects it, and `EnsureImmutableCollection<T>()` checks `collection` for `null` only when it checks the collection (#52); this supersedes parts of the #47 amendment; see the second Amendment under Implementation Notes
- **Amended**: 2026-10-10 — in a call that compares, `EnsureAssignable<T>()` does not compare one instance of a shared type whose state can change, passed as `actual` and as `expected`: it throws `InvalidOperationException` (#54, rule S of [ADR-0022](0022-faithful-old-copies-and-robust-comparison.md)); see the third Amendment under Implementation Notes
- **Amended**: 2026-10-10 — when `T` is a `FrozenSet<T>` or a `FrozenDictionary<TKey, TValue>`, `EnsureAssignable<T>()` compares two values that are not `null` as a whole: element by element, in enumeration order, without the patterns (#49, [ADR-0022](0022-faithful-old-copies-and-robust-comparison.md)); see the fourth Amendment under Implementation Notes
- **Amended**: 2026-10-11 — when `T` is an array type or `System.Array`, `EnsureAssignable<T>()` compares two values that are not `null` as a whole: element by element, without the patterns; the release notes gain one entry marked BREAKING, for the rank, lengths and lower bounds of two arrays, and one entry under Fixed, for arrays of pointers and of function pointers (#57, [ADR-0022](0022-faithful-old-copies-and-robust-comparison.md)); see the fifth Amendment under Implementation Notes

---

Expand Down Expand Up @@ -574,6 +575,33 @@ The decision is unchanged. [Issue #49](https://github.com/cwouyang/uContract.NET

Unchanged: the main decision of this ADR, the check of the pattern array, the `null` rule and its two messages, the rule for one shared instance on both sides (#54), `EnsureImmutableCollection<T>()`, and every other `Contract` method. The public signatures do not change.

### Amendment (2026-10-11): An array type or System.Array as T is compared as a whole; the release notes for #57

The decision is unchanged. [Issue #57](https://github.com/cwouyang/uContract.NET/issues/57) adds one kind of `T` to those that `EnsureAssignable<T>()` compares as a whole. When the declared `T` is an array type or `System.Array`, two values that are not `null` are compared as one pair: element by element, each element as a member or an element is. No member of `T` is compared, and the assignable patterns do not apply and are not examined. The same change makes `EnsureAssignable<T>()` find two arrays equal only when they have the same rank, and the same length and lower bound in every dimension, and compare two arrays of pointers or of function pointers by address. It also makes `Old<T>()` copy an array whose element type is declared in `System.Collections.Frozen`, and a value of a value type that is declared there; `Old<T>()` still shares a `FrozenSet<T>`, a `FrozenDictionary<TKey, TValue>` and every other class that is declared in that namespace. The amendment of [ADR-0022](0022-faithful-old-copies-and-robust-comparison.md) for #57 records the changes, with their reasons, the behaviour before and after, the measurements and the limits.

**Qualified in the #52 amendment.** Each statement is quoted by its opening words:

- Under "Qualified.": "When `T` is a `string`, a delegate type or a nullable value type, two values that are not `null` are now compared as a whole." The #49 amendment above added a `FrozenSet<T>` and a `FrozenDictionary<TKey, TValue>`. The same now holds when `T` is an array type or `System.Array`. The amendment of ADR-0022 for #57 records that decision.
- The table row that ends "Compared; see the amendment of ADR-0022 for #52". For an array type or `System.Array` as `T`, see the amendment of ADR-0022 for #57.

**Qualified in the #49 amendment.** Each statement is quoted by its opening words:

- The Status line of 2026-10-10 for #49: "when `T` is a `FrozenSet<T>` or a `FrozenDictionary<TKey, TValue>`, `EnsureAssignable<T>()` compares two values that are not `null` as a whole". It now also does so when `T` is an array type or `System.Array`.
- "`Old<T>()` is unchanged: it still shares such a value with the original instead of copying it." That stands for a `FrozenSet<T>` and a `FrozenDictionary<TKey, TValue>`. `Old<T>()` no longer shares an array of them: it copies the array, and each element of the copy is the original's set or dictionary.
- Release notes, under "Still true in the #52 amendment": "No entry is added for #49." That is true of #49. For #57 two entries are added; "Release notes" below says which.

**Release notes.** This is what `CHANGELOG.md` holds under `[Unreleased]` for #57:

- **One entry is added under Changed, marked BREAKING.** It begins "`EnsureAssignable<T>()` finds two arrays equal only when they have the same rank, and the same length and lower bound in every dimension." It names the pairs that 2.0.0 found equal and that are a violation now. It says that the rule is for two arrays, and not for an array and another sequence, and not for two arrays that are passed as a `T` that is neither an array type nor `System.Array`. It says that the violation does not name the rank, the length or the lower bound that differs.
- **One entry is added under Fixed.** It is not marked BREAKING. It begins "`Old<T>()` no longer throws `ArgumentException` for an object that holds an array of pointers or of function pointers, and `EnsureAssignable<T>()` no longer throws `NotSupportedException` for such an object or for such an array as `T`."
- **Existing entries are extended.** The entry for a `T` that is compared as a whole, which the #52 amendment names, now also names an array type and `System.Array`, says what 2.0.0 reported for an array as `T`, and carries "(#52, #57; ADR-0022)". The entry that begins "`Old<T>()` copies by reflection instead of JSON" now says that a `FrozenSet<T>`, a `FrozenDictionary<TKey, TValue>` and every other class that is declared in `System.Collections.Frozen` are shared, that a value type declared there and an array whose element type is declared there are copied, and that an element of a copied array or list keeps its own rule. The entry that begins "`EnsureAssignable<T>()` reports more" now says "class" where it said "type" in its sentence on two instances that are compared by reference, no longer gives two arrays of `FrozenSet<T>` as an example there, and points to the new entry for two arrays. The paragraph "Migrating from 2.x" gains sentences on an array as `T` and on the rank, lengths and lower bounds of two arrays.

The reasons. The entry for the rank, lengths and lower bounds is new and is marked BREAKING because calls that passed in 2.0.0 now throw, and no existing entry said so: the pairs that the entry names were measured with the 2.0.0 package and gave no exception there. 2.0.0 did report two one-dimensional arrays of different lengths, so the entry does not say that 2.0.0 compared none of the three; it names the pairs that were measured. The entry for the arrays of pointers and of function pointers is under Fixed: in the eight calls that were measured with the 2.0.0 package, `Old<T>()` threw `ArgumentException` and the comparison threw `NotSupportedException`. No entry is added for the arrays and the value types of `System.Collections.Frozen`: the design gives as the reason that, against 2.0.0, they bring nothing that the existing entries do not already state (A10 in the amendment of ADR-0022 for #57). The #49 amendment above handled #49 in that way.

**Why an amendment and not a new ADR.** The main decision of this ADR stands. The amendment of ADR-0022 for #57 gives the reasoning for all four amended ADRs.

Unchanged: the main decision of this ADR, the check of the pattern array, the `null` rule and its two messages, the rule for one shared instance on both sides (#54), `EnsureImmutableCollection<T>()`, and every other `Contract` method. The public signatures do not change.

---

## References
Expand All @@ -596,3 +624,4 @@ Unchanged: the main decision of this ADR, the check of the pattern array, the `n
| 2026-10-09 | Amended | `EnsureAssignable<T>()` compares `null` as a value; `EnsureImmutableCollection<T>()` checks `null` only when it checks (#52). Supersedes parts of the #47 amendment. Decision unchanged. See Implementation Notes > second Amendment. |
| 2026-10-10 | Amended | One instance of a shared type whose state can change, passed as `actual` and as `expected`, is not compared: `InvalidOperationException` (#54, rule S of ADR-0022). Qualifies parts of the #52 amendment. Decision unchanged. See Implementation Notes > third Amendment. |
| 2026-10-10 | Amended | A `FrozenSet<T>` or a `FrozenDictionary<TKey, TValue>` as `T` is compared as a whole (#49, ADR-0022). Qualifies parts of the #52 amendment. Decision unchanged. See Implementation Notes > fourth Amendment. |
| 2026-10-11 | Amended | An array type or `System.Array` as `T` is compared as a whole; the release notes gain one BREAKING entry (rank, lengths and lower bounds of two arrays) and one entry under Fixed (arrays of pointers and of function pointers) (#57, ADR-0022). Qualifies parts of the #52 and #49 amendments. Decision unchanged. See Implementation Notes > fifth Amendment. |
Loading
Loading