Skip to content

Preserve TypeDelegator and TypeBuilder constant identity - #580

Open
jogibear9988 wants to merge 2 commits into
dadhi:masterfrom
jogibear9988:fix/type-constant-identity
Open

jogibear9988 wants to merge 2 commits into
dadhi:masterfrom
jogibear9988:fix/type-constant-identity

Conversation

@jogibear9988

@jogibear9988 jogibear9988 commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

A TypeDelegator or unfinished TypeBuilder constant is treated as an ldtoken-compatible runtime Type, so strict compilation throws ArgumentException instead of returning the original object. Keep non-runtime Type instances in FEC's existing constants closure. Actual runtime Type values retain token emission and their identity.

One constant-classification condition and a cached concrete runtime Type. The comparison now uses a named RuntimeType field, initialized once, with a source comment explaining why TypeDelegator/TypeBuilder must stay in the closure to preserve identity (review feedback). TypeCode.Object does not distinguish these implementations. Keep the ordinary constant short-circuit order. Shared Standard/Light tests cover identity, repeated constants, null, runtime/generic parameter, byref and array Type values.

This is an independent extraction from #558, based directly on master 750e1e8ea602a7e72a90072ee80b0c1376f5b72d. It has the original fix plus a separate review-clarification commit and does not depend on either of the other standalone fixes. #558 and #559 are unchanged.

Validation

  • The new regressions fail against unmodified master and pass with this fix on .NET 8, .NET 10 and .NET Framework 4.7.2 (4 registered regression methods across the applicable input APIs).
  • Full Release solution build passes with zero warnings/errors.
  • Legacy native suites pass: 1,756 cases on .NET 10, 1,755 on .NET 8, and 1,639 on Framework.
  • The complete Framework runner passes 194 TestX + 1,639 legacy cases. en-US culture is used for existing English exception-message assertions.
  • On .NET 8/10, the full runner still fails ParityTests.Binary_operators_with_methods_and_nullable_operands_are_the_same_as_system for nullable MultiplyChecked in both input variants. The identical failures reproduce on unmodified master. No other fixture failures were observed; the legacy suites were invoked separately because this existing TestX failure gates the normal runner.

Rebase

Rebased onto freshly fetched upstream master 750e1e8e on November 3, 2026. Both test-runner registration conflicts were resolved by retaining master's reduction regressions alongside this PR's identity regressions. The PR remains two focused commits directly above master. All validation results above were rerun after the rebase.

The earlier performance table used the previous master base and has been removed. Performance measurements have not been rerun against this new base; no timing or allocation claims are made for the rebased branch.

dadhi commented Nov 3, 2026

Copy link
Copy Markdown
Owner

The type != typeof(object).GetType() check is correct, but it’s not obvious why we compare against that type. Could we make the intent explicit and evaluate it once?

private static readonly Type RuntimeType = typeof(object).GetType();

// Only the real RuntimeType can be emitted via ldtoken; TypeDelegator/TypeBuilder must stay in the closure to preserve identity.

Then use type != RuntimeType in IsClosureBoundConstant. TypeCode won’t distinguish these types: GetTypeCode returns Object for RuntimeType, TypeDelegator, and TypeBuilder.

jogibear9988 added a commit to jogibear9988/FastExpressionCompiler that referenced this pull request Nov 3, 2026
Cache the concrete runtime Type implementation once and explain why TypeDelegator and TypeBuilder constants must stay in the closure to preserve identity, as requested in the review of dadhi#580.

Verified Standard and Light identity regressions on net8.0, net10.0 and net472; full Release build passes. Legacy suites and Framework runner pass. Existing master nullable MultiplyChecked parity failures remain unchanged. Measured control allocations and decoded IL are unchanged.
Keep TypeDelegator and unfinished TypeBuilder objects in the existing constants closure rather than emitting ldtoken for them. Keep runtime Type token emission and the ordinary constant classification order. Add shared Standard/Light identity and runtime-token regressions.
Cache the concrete runtime Type implementation once and explain why TypeDelegator and TypeBuilder constants must stay in the closure to preserve identity, as requested in the review of dadhi#580.

Verified Standard and Light identity regressions on net8.0, net10.0 and net472; full Release build passes. Legacy suites and Framework runner pass. Existing master nullable MultiplyChecked parity failures remain unchanged. Measured control allocations and decoded IL are unchanged.
@jogibear9988
jogibear9988 force-pushed the fix/type-constant-identity branch from 40213c0 to 55dca61 Compare November 3, 2026 22:40
@jogibear9988

Copy link
Copy Markdown
Contributor Author

Implemented in 55dca61. IsClosureBoundConstant now compares against private static readonly Type RuntimeType = typeof(object).GetType(), so the implementation type is evaluated once. The source comment explains why only real runtime Type objects use token emission, while TypeDelegator/TypeBuilder stay in the closure to preserve their identity. Agreed that TypeCode.Object cannot distinguish these implementations.

The existing identity regressions cover TypeDelegator, unfinished TypeBuilder, repeated references, and runtime Type values for both Standard and LightExpression. They pass on .NET 8, .NET 10, and .NET Framework 4.7.2.

Also rebased onto freshly fetched master (750e1e8), preserving both sets of test registrations. Release solution build passes without warnings/errors; legacy suites and Framework runner pass. The previously observed nullable MultiplyChecked parity failures remain in the .NET 8/10 full runners.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants