Skip to content

Preserve generic class runtime row binding and constructor partitions - #7599

Open
thomhurst wants to merge 18 commits into
mainfrom
issue-7596-runtime
Open

thomhurst wants to merge 18 commits into
mainfrom
issue-7596-runtime

Conversation

@thomhurst

@thomhurst thomhurst commented Nov 4, 2026 •

Copy link
Copy Markdown
Owner

Generic class constructor partitions now retain rows with omitted optional parameters only when every supplied value fits the explicit instantiation. Runtime row handling also preserves wrapped reference-array shape and wrapper depth, reference-tuple payloads, and excess tuple columns for the binder.

The constructor filter recovers array element types and ranks from the existing cached constructor parameter lookup. Generated TypeInfo currently erases open arrays to object; treating that description as assignable admitted string[] into an int[] constructor. The recovered structural description is private and handles arrays nested inside generic parameters without MakeGenericType. No public API or generator output changes are needed. The four array regression cases fail without recovery and pass with it, using both generated-style erased metadata and closed reflection metadata. The wrapped TestDataRow<string[][]> reproduction is excluded for explicit <int,int> arguments.

This is the runtime prerequisite split from #7571, not its discovery adapters. Tests exercise the existing emitted metadata contract directly; enabling the new inference and its complete discovery fixtures remains in #7597.

Review dispositions: retain the existing per-instantiation method-source settings because the reader and instance conversion consume them; a wrapper would need to preserve attribute identity and conversion too. Other source mappings remain weakly held. Code now documents declared mapping precedence over method-source declarations and runtime inference, and explicitly groups the optional-row condition. Nested generic covariance and value-type/invariant rejection remain covered. No performance improvement is claimed; retention measurement and enum policy remain in #7588.

Validation:

  • net10.0 focused unit suites in generated and --reflection execution: GenericClassSourceRuntimeTests (21 each), MethodDataSourceDeclaredRowTypeTests (25 each), DataSourceHelpersTests (9 each).
  • GenericClassRowPartitionCleanupTests: 2 passed.
  • Focused osx-arm64 Native AOT executable published and ran successfully, checking valid/invalid arrays and arrays nested in generic interfaces; no trimming or AOT warnings. Evidence project uses the changed Core assembly and its existing signed unit-test friend identity.
  • git diff --check passed. No generator output or shipped public API changed, so snapshot updates are unnecessary. Engine test project was not run locally, per repository instructions.

Closes #7596

Latest review fix (66dd2ba): array supertypes are accepted in reverse structural comparisons, including vector collection interfaces with reference covariance and value-type rejection. Regression checks preserve all compatible rows with null-row/AllRows fallback for int/string vectors and matrices, comparing generated-style and closed reflection metadata. GenericClassSourceRuntimeTests: 29/29 generated and 29/29 reflection (net10.0). Core netstandard2.0 build: zero warnings/errors. Focused osx-arm64 Native AOT publish and executable pass with no warnings.

Summary by CodeRabbit

  • Bug Fixes
    • Improved matching and partitioning of generic test data, including rows from method sources without a class-providing source.
    • Preserved rows with omitted or optional constructor values, trailing collections, arrays, nested generics, nullable values, and compatible types.
    • Fixed tuple-based test data conversion to preserve additional values and correctly handle wrapped or spread reference tuples.
    • Ambiguous data-source method matches now avoid incorrect declared-type inference.

Latest review fix: preserve expanded and omitted trailing constructor arrays and params interfaces, including scalar conversion before collection, and numeric-to-enum values accepted by CastHelper. Regression cases fail on the prior matcher. GenericClassSourceRuntimeTests pass 34/34 generated and 34/34 reflection on net10.0; Core netstandard2.0 builds with zero warnings/errors. Focused osx-arm64 Native AOT publish and execution pass with no warnings. No shipped public API or generator output changes; no Engine.Tests ran locally.

Latest review fix (68e7459): preserve null-to-default constructor values, element-wise trailing-array conversion, CLR vector covariance for generic collection interfaces, and already accepted ITuple payloads containing TestDataRow. Regression cases fail against the prior code. GenericClassSourceRuntimeTests: 41/41 generated and 41/41 reflection; DataSourceHelpersTests: 12/12 each on net10.0. Core netstandard2.0: zero warnings/errors. Focused osx-arm64 Native AOT publish and executable pass with no warnings. No public API/generator output changes; no Engine.Tests run locally.

Latest review fix (2aa08c4): retain enum-to-primitive rows and registered conversions to structurally described constructed parameters, matching existing closed registry targets without constructing generic types or invoking converters. GenericClassSourceRuntimeTests pass 50/50 generated and 50/50 reflection; Core netstandard2.0 builds with zero warnings/errors; focused osx-arm64 Native AOT publish/executable pass without warnings. Three new regression cases fail before the fix. Metadata-shape and matcher consolidation follow-up: #7615.

@thomhurst

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-05T01:32:45.157998Z 762f5b7 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Review of #7599

I read the diff only. I did not build it or run the tests.

Overall: The change is sound and the reasoning is well documented. Each fix has a matching regression test, and the array-erasure bug (a string[] admitted into an int[] constructor) is a real one. Recovering the shape from the cached constructor lookup, instead of changing generator output, keeps the public API and snapshots untouched.

Observations (none blocking):

  1. Size of GenericClassRowPartition.cs. About 200 lines of structural type matching now sit in a partition class. The IsValueType switch is written out twice, once local to the method and once inside MatchesConstructed. A small private resolver would remove that duplication, and moving the matcher into its own internal static class would make it testable without going through the partition.
  2. Vector versus multi-dimensional array detection. type.Name.EndsWith("[]") is a string heuristic used in several places. Use Type.IsSZArray where the target framework has it, or wrap the check in one helper.
  3. Recovery through ReflectionInfo. HasErasedArray also matches a genuine object argument such as IEnumerable<object>. Those cases pay the lookup once and the result is cached, so this is fine, but a comment saying so would help.
  4. catch (AmbiguousMatchException) in GetDeclaredMemberType. Returning null and letting the later read report the failure is sensible. Check that a test covers the later read surfacing the error.
  5. CopyTupleElements(tuple, tuple.Length). This changes behaviour for every typed tuple row, not just the generic-class path. The new test covers it. It is worth a release-notes line if users can observe it.

No blocking issues. LGTM from my reading, pending CI.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a69f8307e7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@greptile-apps

greptile-apps Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 4/5

[Medium impact] The changes affect generic-class data rows and tuple binding in TUnit test runs.

Fix ordinary array matching before merging; it can add constructor rows that fail.

Findings

  1. P1 Invalid array rows now run ▶

Summary

This PR expands generic constructor-row matching and preserves runtime row shapes.

  • Generic-class rows stay with an instantiation when their values fit its constructor.
  • Method-source rows keep their declared shape during generic inference.
  • Tuple rows keep their full shape through constructor binding.

Reviews (22) · Last reviewed commit: "Fix #7596 engine expectations for retain..." · Reviewed by Greptile

@coderabbitai

coderabbitai Bot commented Nov 4, 2026 •

Copy link
Copy Markdown

Explain Change →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • Resume reviews
  • Trigger review
Walkthrough

The change updates runtime data-row binding and generic constructor partitioning. It adds declared row-shape metadata, preserves tuple values during conversion, and matches method-source rows against constructor parameter types.

Changes: Runtime row binding

Layer / File(s) Summary
Normalize declared and tuple row shapes
src/TUnit.Core/Attributes/TestData/MethodDataSourceAttribute.cs, src/TUnit.Core/Helpers/DataSourceHelpers.cs, tests/TUnit.UnitTests/MethodDataSourceDeclaredRowTypeTests.cs, tests/TUnit.UnitTests/DataSourceHelpersTests.cs
Declared row-type lookup reports wrapper removal and reference-array spreading, and returns null for ambiguous member lookup. Tuple conversion preserves whole reference tuples and excess tuple elements. Tests cover these row shapes and ambiguous lookup.
Match method-source rows to constructors
src/TUnit.Core/Attributes/TestData/GenericClassRowPartition.cs
Method-source rows can match constructor parameters through assignability and supported conversions. Matching handles optional and trailing collection parameters, recovered array and generic type shapes, variance, inheritance, and nullability.
Validate constructor-row matching
tests/TUnit.UnitTests/GenericClassSourceRuntimeTests.cs
Tests cover params collections, optional and omitted values, conversions, array shapes, nested generics, variance, and row retention with generated and reflected metadata.

Estimated code review effort: 4 (Complex) | ~45 minutes

Change: Bug fix

Possibly related issues

  • Issue 7543: The PR changes generic class constructor partitioning and method-source row handling. Inherited-source composition remains separate.
  • Issue 7445: The PR changes constructor-row filtering, optional-row handling, and collection binding.
  • Issue 7443: The PR changes generic class row partitioning; reuse of evaluated row state across discovery modes is not described here.
  • Issue 7588: The PR covers constructor row shapes, optional binding, tuple handling, and collecting-tail behavior.
  • Issue 7446: The PR supplies runtime support for generic class method-source rows.
  • Issue 7365: The PR filters rows against constructor parameter types and preserves original rows.
  • Issue 7597: The PR changes shared tuple normalization and constructor-array matching behavior.
  • Issue 7333: The PR adds runtime row handling and constructor partitioning relevant to generic class inference.
  • Issue 7224: The PR changes runtime row-shape and constructor-binding support relevant to reflection inference.
  • Issue 7367: The PR changes whole-versus-spread tuple binding behavior.
  • Issue 7340: The PR preserves tuple columns and supports optional or collecting constructor parameters.
  • Issue 7587: The PR covers wrapped reference tuples, excess tuple columns, and trailing collection rows.

Possibly related PRs

  • thomhurst/TUnit#7422: Adds generic-class method-source inference and runtime row partitioning, which this PR extends with constructor matching and row-shape handling.
  • thomhurst/TUnit#7339: Introduces GenericClassRowPartition, which this PR extends with optional-row and type matching.
  • thomhurst/TUnit#7571: Uses runtime support for generic class inference that this PR supplies, including optional constructor rows and tuple binding.
Merge Risk: Low · previous review 03540

The trailing-array matching change is consistent with constructor binding. A narrow tuple-row case can still pass the wrong value or skip a test; merge with owner awareness of that unresolved issue.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 29.17% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed

Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly summarizes the main changes: runtime row binding for generic classes and constructor partition preservation.
Linked Issues check Passed The current PR evidence supports the active coding requirements in #7596. Runtime partitioning retains supplied and omitted optional rows only when values match the explicit constructor. Constructor m…
Out of Scope Changes check Passed The reported changes are connected to #7596. They modify runtime constructor partitioning, method-source row handling, tuple binding, and focused tests. The evidence reports no shipped public API, dis…
Full details: Linked Issues check

Explanation

The current PR evidence supports the active coding requirements in #7596. Runtime partitioning retains supplied and omitted optional rows only when values match the explicit constructor. Constructor matching covers arrays, ranks, nested arrays, collection supertypes, variance, conversions, and rejection of incompatible value-type or invariant cases. Method-source handling preserves reference-array spreading and wrapper information. Data-source helpers preserve whole reference tuples, nested TestDataRow payloads, and excess tuple columns. Focused tests cover generated and reflection metadata, and the reported Native AOT publish and execution passed without warnings. The changes do not enable discovery inference.

Full details: Out of Scope Changes check

Explanation

The reported changes are connected to #7596. They modify runtime constructor partitioning, method-source row handling, tuple binding, and focused tests. The evidence reports no shipped public API, discovery adapter, generator factory, integration snapshot, retention, or enum-policy changes. No unrelated change is demonstrated.

Full details: Docstring Coverage

Explanation

Docstring coverage is 29.17% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 24 functions across 6 files.

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Finishing Touches

Generate docstrings

  • Commit to this branch
  • Create a new PR
Generate unit tests (beta)

  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

Share

A rabbit checks each row in line
And keeps the tuple columns fine
Through arrays, types, and defaults too
The matching paths now sort what’s true
The rows stay whole as tests pass through

@thomhurst

Copy link
Copy Markdown
Owner Author

Review follow-up pushed in ae41e65.

Claude observations disposition:

  1. Removed the duplicated value-type switch by reusing the local resolver. Keeping the private matcher in the partition bounds this correctness fix; extraction is optional cleanup, with behavior exercised through the existing internal row-matching seam.
  2. Retaining the vector-name check for the netstandard2.0 target, where Type.IsSZArray is unavailable. Array rank and element validation remain covered; a conditional helper is optional cleanup.
  3. Added the comment explaining the one-time lookup for genuine object arguments.
  4. Extended the ambiguous-source test to verify that the later runtime read throws AmbiguousMatchException, alongside the null declared-type result.
  5. The PR body records the observable tuple behavior (preserving excess columns for the binder); retaining that as the change note for this fix.

Validation: GenericClassSourceRuntimeTests 25/25 and MethodDataSourceDeclaredRowTypeTests 25/25, each in generated and --reflection execution on net10.0. Core netstandard2.0 builds with zero warnings/errors. A focused signed Native AOT executable publishes with no warnings and runs successfully for nullable boxing, object contravariance and inherited generic supertypes, checking valid and incompatible values. git diff --check passes. No generator output or shipped public API changed. Engine.Tests was not run locally, per repository instructions.

The PR remains draft for fresh Claude and Codex review of this head.

@thomhurst

Copy link
Copy Markdown
Owner Author

@codex review

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Review of #7599

I read the diff for GenericClassRowPartition.cs, MethodDataSourceAttribute.cs and DataSourceHelpers.cs. I did not build the PR or run its tests, and I did not read the DataSourceHelpersTests.cs changes.

Looks good

  • The type matching avoids MakeGenericType, which keeps it compatible with Native AOT. Variance is applied only to reference-type arguments, and Nullable<T> is unwrapped only when checking assignment.
  • The AmbiguousMatchException catch in GetDeclaredMemberType returns null, so the eventual read reports the failure.
  • Optional-row retention now requires every supplied value to fit the explicit instantiation. That fixes the string[] into int[] admission.

Suggestions (design, not blockers)

  1. Hand-rolled assignability matcher. MatchesConstructorParameterType, Substitute and MatchesConstructed re-implement CLR assignability and variance rules, including base types, interfaces, arrays and nullables. This is about 150 lines of subtle logic that must stay correct. It would be easier to maintain if it moved to its own internal static class, such as ConstructorTypeMatcher, so it can be unit-tested on its own and GenericClassRowPartition stays focused on partitioning. If Complete method-source generic class discovery parity and fix tuple normalization #7597 does not need the matcher inside this file, I would split it out.
  2. Erased-array recovery. HasErasedArray treats any ConcreteType of object as potentially an erased array. As the code comment says, a genuine object parameter triggers one reflection lookup and caches object. That is acceptable. Emitting a proper array description from the generator would remove the need for this recovery, so consider a follow-up issue.
  3. Array shape detection. type.Name.EndsWith("[]") is used in three places to tell vectors from rank-1 multidimensional arrays. Type.IsSZArray is not available on every target framework, so a single helper with a comment explaining that would remove the duplication.
  4. Nulls for optional parameters. For a null supplied to a ConstructedGeneric parameter, the value-type check uses GenericDefinition, so Nullable<> is special-cased. A short test for a null supplied to a Nullable<T> parameter and to a non-nullable generic struct parameter would pin this down, if it is not already covered.

Overall the behaviour is well reasoned and tested. The main concern is maintainability of the custom type matcher. Approving in spirit; the suggestions above are optional.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ae41e655d4

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@thomhurst

Copy link
Copy Markdown
Owner Author

@codex review

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Review of #7599

I read the diff and the earlier review comments. I did not build the PR or run the tests.

Looks good

  • The matcher walks TypeInfo and open generic definitions, so it never calls MakeGenericType. That keeps it Native AOT-safe, and the PR description says an AOT publish ran clean.
  • Variance is handled correctly. It applies only to reference-type arguments, Nullable<T> unwraps only in the assignment direction, and arrays keep rank and vector shape.
  • The string[] into int[] bug has regression tests for both generated-style erased metadata and closed reflection metadata.
  • ConditionalWeakTable caches the recovered constructor types and interface lists, so reflection stays off the per-row hot path. The IL2070 suppression is justified in a comment.
  • Dropping the tuple column cap (CopyTupleElements(tuple, tuple.Length)) lets the binder see excess columns instead of silently discarding them. That is the better behaviour, and a test covers it.
  • GetDeclaredMemberType now returns null on AmbiguousMatchException and leaves the failure to the later read, which is reasonable.

Suggestions (non-blocking)

  1. MatchesConstructorParameterType is about 150 lines. It has nested local functions, and a signed direction int drives it. A small private enum Variance { Covariant, Contravariant, Invariant } would be clearer than 1/-1/0, and a separate ConstructorTypeMatcher class would make it unit-testable on its own and shrink GenericClassRowPartition. This is optional here, but it will matter if Complete method-source generic class discovery parity and fix tuple normalization #7597 builds on it.
  2. type.Name.EndsWith("[]") is used to tell vectors from multidimensional arrays. type.IsSZArray exists on netstandard2.1+ and net5+. Keep the name check for netstandard2.0, but put it in one helper, because it currently appears three times.
  3. HasErasedArray treats any object parameter as a possible erased array, so genuine object parameters trigger one recovery each. The comment mentions this, and the result is cached, so the cost is small.

No blocking issues. The behaviour is well covered by tests, and the remaining items from earlier review rounds look addressed.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Breezy!

Reviewed commit: 66dd2ba2e0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@thomhurst
thomhurst marked this pull request as ready for review November 4, 2026 19:06
@thomhurst

Copy link
Copy Markdown
Owner Author

Marked ready after verifying positive Claude and Codex verdicts for 66dd2ba and all review threads resolved. The head is unchanged after promotion.

Waiting for the ready-for-review Claude run 41893737866 and CI. Auto-merge has not been armed: repository guidance requires the fresh Claude run to pass and its findings to be read first. The next unit should disposition any new findings, then use scripts/Enable-AutoMerge.ps1 when its gate passes.

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Review of #7599

I read the diff and did not run the tests.

What looks good

  • The optional-row filter now checks every supplied value against the explicit instantiation. That fixes the string[] into int[] admission.
  • It avoids MakeGenericType, which keeps it AOT-safe. The IL2070 suppression on GetConstructorRowInterfaces is justified in its comment.
  • The tests are thorough. They cover array rank, nested generics, variance, value-type rejection, and both generated-style (erased) and closed-reflection metadata.
  • CopyTupleElements(tuple, tuple.Length) keeps excess columns so the binder sees them. The new tests cover that.
  • GetDeclaredMemberType now returns null on AmbiguousMatchException, so the later read reports the failure instead of discovery crashing.

Concerns (design, not blockers)

  1. Size and cohesion. MatchesConstructorParameterType is about 150 lines of hand-written assignability logic in GenericClassRowPartition.cs. It covers variance, Nullable, array covariance, vector collection interfaces and substitution of open supertypes. It re-implements part of Type.IsAssignableFrom and is easy to get subtly wrong. I'd move it, along with ConstructorArray, DescribeConstructorType and the caches, into its own internal static class (for example ConstructorTypeMatcher). It could then be unit-tested directly and wouldn't bloat the partition type.
  2. Duplicated structural conversion. DescribeConstructorType and the local Substitute both convert a Type into a TypeInfo, and Substitute repeats the type.Name.EndsWith("[]") vector check. A single shared helper would remove that. Better still, type.IsSZArray is available on newer TFMs and is clearer than the name check. If Directory.Build.props multi-targets netstandard2.0, keep the name check but put it in one place.
  3. Caching. ConditionalWeakTable<Type, Type[]> for GetInterfaces() is fine. ConstructorTypes keyed on ParameterMetadata also looks fine, but the comment says it recovers 'once' for genuine object parameters. A parameter that really is object is therefore recovered on first use and then returns object again. Please confirm that path is allocation-free after the first call, since the filter runs per row.
  4. GetDeclaredRowType overloads. There are now three public-ish overloads, with out flags for spreadsReferenceArray and rowWrapperRemoved. Returning a small readonly struct such as DeclaredRowShape(Type? Type, bool Spread, bool WrapperRemoved) would be easier to read than chained overloads that discard outs.
  5. Tuple edge case. The singleton ValueTuple branch in DataSourceHelpers relies on UnwrapTupleAot(item) is [ITestDataRow wrapped], which allocates on that path. It only runs for singleton tuple rows, so it's acceptable, but it's worth a note.

No correctness bugs found. Item 1 is the main suggestion, and the others can be follow-ups.

@thomhurst

Copy link
Copy Markdown
Owner Author

The previous blocker is cleared: promotion Claude run 41893737866 passed, and its review found no correctness blockers. Its optional extraction/helper/row-shape refactors remain follow-up suggestions; the cached constructor recovery uses ConditionalWeakTable.GetValue with a static callback, so genuine object parameters reuse the stored TypeInfo after first recovery. The singleton tuple allocation is acknowledged; no allocation or performance improvement is claimed.

Pushed 731e334 to fix the failed generator snapshots: #7594 changed STAThreadTests from Repeat(100) to Repeat(10) and moved method lines without updating the four framework snapshots. Reviewed the Ubuntu net10 and Windows net472 received artifacts, preserved snapshot encoding, and applied the same source-derived count/line changes to net8/net9. Targeted STAThreadTests generator snapshot test passes on net10 (1 passed); git diff --check passes. Other runtime versions and Windows net472 are unavailable locally and remain CI validation. No received files committed. Existing analyzer warnings are tracked by #7410.

The Windows Engine failure in run 41893737499 matches the missing-TRX reflection pass failure tracked by #7554, also recurring on main. #7603 is investigating it; its newer diagnostic run 41901280925 is still in progress. No unchanged-head rerun, timeout increase, or speculative engine fix was made. No Engine.Tests ran locally.

Resolve routes checked: this PR remains the implementation of #7596 and retains Closes #7596; main does not contain its runtime fix and no replacement supersedes it. Parent closure, stale issue criteria, and a human decision do not apply. This unit pushed a concrete snapshot correction rather than deferring. Fresh Claude/Codex review and CI are required for the new head before merge; auto-merge remains unarmed.

@github-actions

github-actions Bot commented Nov 4, 2026

Copy link
Copy Markdown
Contributor

Review of #7599

I read the diff and the earlier review comments. I did not build the PR or run its tests.

Verdict: The change is sound, and the fixes target real bugs. Examples are string[] being admitted into an int[] constructor, and tuple rows losing excess columns. Recovering array shape from the cached ReflectionInfo avoids changing generator output or public API, and the PR description explains why.

The STAThreadTests snapshot updates (Repeat(100) to Repeat(10), shifted line numbers) match the current tests/TUnit.TestProject/STAThreadTests.cs. They only bring stale snapshots up to date, so I have no concern there.

Suggestions (none blocking):

  1. Extract the structural matcher. GenericClassRowPartition.cs gains about 250 lines of type-description matching: MatchesConstructorParameterType, DescribeConstructorType, Substitute and the variance logic. A ConstructorTypeMatcher internal static class would be easier to unit test directly and would keep the partition class focused on partitioning.
    • The IsValueType switch is written out twice, once in MatchesOmittedOptionalConstructorRow and once as a local function. Both could use one shared resolver.
    • The Substitute local duplicates part of DescribeConstructorType. Giving DescribeConstructorType an optional substitution map would remove it.
  2. Vector detection by name. type.Name.EndsWith("[]") appears in several places, in both GenericClassRowPartition and MethodDataSourceAttribute. Type.IsSZArray is available on modern TFMs. For netstandard2.0, a single helper (IsVector) would be safer and give one place to fix. A multi-dimensional array of rank 1 (T[*]) is a name-based edge case.
  3. CopyTupleElements(tuple, tuple.Length). This applies to every ITuple row, not only the generic-class path. The comment explains why, and the PR says it is tested. Please confirm that existing tests cover the case where a tuple is longer than the parameter count, with no trailing params array. Users should get a clear binder error there instead of a silent drop.
  4. Weak cache keyed on ParameterMetadata. ConditionalWeakTable<ParameterMetadata, TypeInfo> is fine. The HasErasedArray check also matches genuine object arguments, and the cached description stays object in that case. The comment already notes this.
  5. GetDeclaredMemberType swallowing AmbiguousMatchException. This is reasonable because the later read reports the failure. Please make sure a test asserts that the later read throws, so the error cannot be lost.

Overall this is good work and I'd be happy to see it merged once CI is green.

@thomhurst

Copy link
Copy Markdown
Owner Author

The original waiting reason is obsolete: Claude run 41893737866 passed. Current head 731e334 also has a positive Claude review (41903641256) and a completed Codex summary with has_any_findings=false for this exact SHA. All five review threads have dispositions and remain resolved. Auto-merge remains unarmed because Engine tests (windows-latest) failed in 41903646332 with the missing-TRX reflection hang tracked by #7554.

Current review dispositions: Claude's matcher extraction, shared vector/type-description helpers and duplicate resolver suggestions are nonblocking follow-ups; keeping this correctness fix focused is reasonable. Ambiguous_Source_Has_No_Usable_Declared_Type explicitly asserts that reading throws AmbiguousMatchException after declared inference returns null. Tuple tests preserve excess columns for the binder; this is not a claim of new end-to-end binder-error coverage. The genuine-object cache and singleton tuple allocation were acknowledged in the previous disposition. CodeRabbit's request to revert STA snapshots is declined: #7594 changed the source and #7603 has since merged the snapshot repair into main; reverting would reintroduce stale expected output. Its docstring percentage is advisory, not a repository requirement. Greptile reports no blockers.

Released this PR lock and worked #7554 under its own lock. Read the completed newer diagnostic capture; recorded concrete loader ownership, thread ages and CPU evidence at #7554 (comment). Started analysis run https://github.com/thomhurst/TUnit/actions/runs/41908125296 on main against this PR's new dump (source 41903646332, exact artifact 13358839692). This is a new-capture diagnostic dispatch, not an undiagnosed rerun of the failing check. Next inspect that analysis before choosing an executor fix; no local Engine.Tests ran.

All Resolve routes checked: #7596 remains open and delivered by this PR with Closes #7596; no replacement supersedes its runtime change. #7603 merged diagnostics rather than fixing #7554. Finished-parent closure, stale criteria and owner decisions do not apply. No on-hold label or repeated deferral added. Waiting now concerns #7554's new diagnostic run, not the completed Claude run.

@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Review of #7599

I read the new constructor-row matching in GenericClassRowPartition.cs. I did not build the PR or run its tests. The behavior is sensible: rows with omitted optional parameters are kept only when every supplied value fits the explicit instantiation. The string[] into int[] admission bug is fixed by recovering array shape from the cached constructor parameter lookup. The PR description covers the review dispositions and the deferral of related work to the follow-up issues.

Concerns (design, not blocking)

  1. Parallel conversion logic. MayConvertConstructorValue re-implements much of CastHelper's conversion rules for partitioning: IConvertible, IParsable, enum, structural operators, and the vector fallback. It has to be kept in sync by hand. The TODO referencing issue 7615 acknowledges this. A single shared 'can bind' predicate that the binder also uses, or a parity test that runs the same rows through both the matcher and the binder, would stop the two drifting apart.
  2. Over-retention is the safe failure mode, but it is silent. The matcher returns true whenever a conversion is uncertain (enumerables, custom ToType, AOT-uncertain parsing). Rows can therefore be retained and then fail at bind time with a less clear error than they would at partition time. A test pinning that behavior would document it.
  3. Complexity. MatchesConstructorRow and MayConvertConstructorValue are long, branchy, and heavy on #if NET. Splitting the trailing-params handling, the array handling, and the scalar conversion handling into separate small methods or types would make each easier to test and review.
  4. Hot-path cost. The interface lookups and the Enum.ToObject try/catch run per row. If the interface lookups are cached as described, this is fine. The PR makes no perf claim.

Overall, this is a solid prerequisite split with good regression coverage, and it keeps Native AOT in mind. No blocking issues from me.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4db92b2d05

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +559 to +561
catch (ArgumentException)
{
return false;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Retain custom IConvertible candidates for enum targets

For an explicit C<MyEnum,string> with constructor C(T first, U second = default!), own-class rows [MyEnum.A] and [customValue] silently lose the second test when customValue implements IConvertible.ToType returning MyEnum. Enum.ToObject rejects a custom value reporting TypeCode.Object, and this catch returns before the deferred custom-IConvertible handling. Unlike the repaired DateTime and structural-target cases, this enum-specific early return still bypasses that handling. CastHelper.TryReflectionConversion successfully binds the value through Convert.ChangeType, so both rows previously survived AllRows, and reflection discovery still retains them. Preserve the custom conversion candidate without invoking its conversion methods during partitioning.

AGENTS.md reference: AGENTS.md:L7-L7

Useful? React with 👍 / 👎.

@thomhurst

Copy link
Copy Markdown
Owner Author

Rebased onto main 1a40c80 and pushed 762f5b7. Resolved the DataSourceHelpers conflict with #7608 by preserving both main's matching first-tuple/optional-parameter handling and retained mutable TupleArgument values, and this PR's singleton wrapped reference-tuple row handling. Range-diff confirms the other 17 commits are unchanged.

Validation on SDK 10.0.403/net10.0: GenericClassSourceRuntimeTests 77/77 generated and 77/77 reflection; DataSourceHelpersTests 12/12 in each mode; targeted TUnit.TestProject.Bugs._7212 fixtures 49/49 in each mode. git diff --check passes. Existing analyzer build warnings remain tracked in #7410. No Engine.Tests ran locally. No generator output/public API changes; hosted CI must revalidate AOT on the rebased head.

The quoted wait for #7554 run 41908125296 no longer applies: it completed successfully and mitigation #7606 merged. Checked Resolve routes: #7596 is still delivered by this PR with its Closes link, main does not contain this runtime fix, no replacement supersedes it, and no completed parent, stale scope, human choice, upstream dependency, credentials or competing lock prevents progress. The remaining #7554 investigation does not block this PR. All inline threads were resolved; Claude's prior-head verdict had no blockers, and current-head reviews must run again. Design/performance follow-ups remain #7615 and #7588. Auto-merge remains unarmed pending fresh review and CI. This unit fixes the merge conflict rather than deferring.

@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Review of #7599 (follow-up)

I read the diff and the earlier review comments. I did not build the PR or run its tests.

Overall: The change is sound. The array-erasure fix is real: string[] was admitted into an int[] constructor. Recovering the array shape from the cached constructor lookup avoids changing generator output or the public API. The PR description says the regression cases fail without the recovery and pass with it.

Suggestions (none blocking):

  1. Matcher size. GenericClassRowPartition.cs now holds a few hundred lines of structural type matching: MatchesConstructorRow, MayConvertConstructorValue, MatchesConstructorParameterType and MatchesStructuralOperator. Moving them into a dedicated internal static class would make them unit-testable without the partition. That also fits the consolidation planned in Preserve generic array shape in generated TypeInfo metadata #7615, and the move is cheap now.
  2. Vector check. Name.EndsWith("[]") appears in several places to tell vectors from multi-dimensional arrays. One helper would keep the checks consistent, and Type.IsSZArray is available on the net targets.
  3. Conversion probing. MayConvertConstructorValue calls Convert.ChangeType and Enum.ToObject in try/catch while partitioning. These only run on known framework types, so the risk is low. Exception-driven rejection can be slow when many rows fail, though. Since Simplify declared row shape and generic inference policy inputs #7588 owns the measurements, please record a baseline there.
  4. Duplicated IsValueType logic. The same TypeInfo switch appears more than once. A small shared resolver would remove the duplication.

The earlier review's points still apply and none of them block the merge.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 762f5b7b1d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// Each instantiation reads its own overload. Generated constructor partitions also retain the integer
// [Arguments(1)] row for the string instantiation because CastHelper converts it to "1".
// Reflection partitions those explicit argument rows by their declared types.
var expected = testMode == TestMode.Reflection ? 4 : 5;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restore matching discovery results across execution modes

For OwnClassOverloadedMethodDataSourcesWithArguments, this changes the regression test to accept five generated tests but only four reflection tests. The extra integer row now runs on the string instantiation only in generated mode, so switching discovery modes changes which tests execute. The repository explicitly requires identical metadata-collection behavior between these modes; reconcile the partitioning behavior and retain a common expected count instead of encoding the divergence in the test.

AGENTS.md reference: AGENTS.md:L7-L7

Useful? React with 👍 / 👎.

return false;
}

var (values, _) = TestDataRowUnwrapper.UnwrapArray(row);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve wrapped tuple arguments during compatibility checks

For an explicit C<int,string> with constructor C(Tuple<int> first, U second = default!), an own-class source returning object[] rows [new TestDataRow<Tuple<int>>(Tuple.Create(1))] and [null] silently loses the tuple row. This untyped unwrap spreads its payload into [1], which fails compatibility with Tuple<int>, while the null row activates filtering and disables AllRows. The execution path in DataUnwrapper.UnwrapWithTypes preserves the wrapped tuple using the expected parameter type, so the discarded row binds successfully and previously survived fallback. Apply the same tuple-preservation decision here; reflection discovery also retains this deferred own-class row.

AGENTS.md reference: AGENTS.md:L7-L7

Useful? React with 👍 / 👎.

Comment on lines +457 to +458
if (value is Array { Rank: 1 } array && array.GetLowerBound(0) == 0
&& MatchesTrailingArrayElements(array, arrayElement, classTypeArguments))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Invalid array rows now run

For C<T,U>(T first, U second = default!) instantiated as <Guid[],int>, consider an own-class source containing [new Guid[] { Guid.Empty }, 1] and [new string[] { "00112233-4455-6677-8899-aabbccddeeff" }]. Previously only the first row survived. This check now keeps the second because MatchesTrailingArrayElements allows string-to-Guid parsing.

But the first parameter is not a trailing array. Its CastHelper.Cast<Guid[]> call converts elements with Convert.ChangeType, which cannot convert strings to Guid. A previously passing class therefore gains a failing constructor row. Match ordinary array conversions separately from trailing arrays, which use Cast<T> for each element.

@thomhurst thomhurst added the needs-human Requires an owner decision or action before automated work continues label Nov 5, 2026
@thomhurst

thomhurst commented Nov 5, 2026 •

Copy link
Copy Markdown
Owner Author

The earlier wait is cleared: run 41908125296 completed successfully. #7554 remains open as an intermittent Windows reflection hang investigation, but that successful run is no longer a blocker for this PR.

Current head: 762f5b7. Four blocking findings remain:

This PR has at least 21 automated review rounds and still has blocking findings. Its linked issue #7596 already carries . The issue-pr-loop rework-budget rule forbids another fix push and directs an already-split PR that exceeds the budget to needs-human. I checked the alternative routes: this is not already implemented, superseded, a finished parent, or merely waiting on CI; another automatic split would repeat the already-split lifecycle.

Owner decision needed: authorize a bounded additional repair/review cycle on this branch, or abandon this implementation and choose a narrower replacement design. I recommend one explicitly authorized cycle addressing all four findings together, restoring a common discovery expectation and preserving binder-compatible enum/tuple rows with generated/reflection regressions and AOT validation. Remove needs-human and state that authorization to resume. Until then, do not merge this head or treat pending CI as the remaining gate.

Triage rechecked against origin/main 1a40c80: head unchanged, no later owner authorization, no replacement PR, and all four findings remain unresolved. Retaining needs-human under the already-split rework-budget rule. No code changes or test runs in this triage unit.

This branch was previously deployed

1 inactive deployment
Pull Requests — 762f5b7b Deployed Nov 5, 2026 by thomhurst via modularpipeline (macos-latest) #21308
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-human Requires an owner decision or action before automated work continues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Preserve runtime data-row binding and constructor partitions for generic class inference

1 participant