Skip to content

[typeless extension receivers] Tests for default-literal receivers#83402

Draft
CyrusNajmabadi wants to merge 204 commits intodotnet:features/extension-members-on-typeless-receiversfrom
CyrusNajmabadi:extension-members-on-typeless-receivers/DefaultLiteral/Tests
Draft

[typeless extension receivers] Tests for default-literal receivers#83402
CyrusNajmabadi wants to merge 204 commits intodotnet:features/extension-members-on-typeless-receiversfrom
CyrusNajmabadi:extension-members-on-typeless-receivers/DefaultLiteral/Tests

Conversation

@CyrusNajmabadi
Copy link
Copy Markdown
Contributor

@CyrusNajmabadi CyrusNajmabadi commented Apr 25, 2026

Championed issue: TBD
Speclet: TBD
Test plan: TBD

Summary

  • Four test files covering the four shape combinations on a default-literal receiver.
  • Tests default-to-int, default-to-string, generic-extension property cases.

Stacked:

  1. [typeless extension receivers] Initial binder support #83348
  2. [typeless extension receivers] Modern (C#14) extension support #83352
  3. [typeless extension receivers] Tests for collection-expression receivers #83397
  4. [typeless extension receivers] Allow lambda receivers #83355
  5. [typeless extension receivers] Tests for lambda receivers #83398
  6. [typeless extension receivers] Allow method-group receivers #83360
  7. [typeless extension receivers] Tests for method-group receivers #83399
  8. [typeless extension receivers] Allow target-typed new() receivers #83365
  9. [typeless extension receivers] Tests for new() / new(args) receivers #83400
  10. [typeless extension receivers] Allow null-literal receivers #83370
  11. [typeless extension receivers] Tests for null-literal receivers #83401
  12. [typeless extension receivers] Allow default-literal receivers #83375
  13. [typeless extension receivers] Tests for default-literal receivers #83402 (this PR)
  14. [typeless extension receivers] Allow conditional-expression receivers #83380
  15. [typeless extension receivers] Tests for conditional-expression receivers #83403
  16. [typeless extension receivers] Allow switch-expression receivers #83385
  17. [typeless extension receivers] Tests for switch-expression receivers #83404
  18. [typeless extension receivers] Allow tuple-literal receivers #83390
  19. [typeless extension receivers] Tests for tuple-literal receivers #83405
  20. [typeless extension receivers] Pin tests excluding throw expressions #83406
  21. [typeless extension receivers] Fix Debug.Assert in dynamic-arg extension diagnostic #83414
  22. [typeless extension receivers] Allow extension indexers #83415
  23. [typeless extension receivers] Tests for extension indexers #83416

Cyrus Najmabadi added 30 commits April 25, 2026 15:32
…onversionFromExpression for typeless source
…rted forms are not forced through BindToNaturalType
…llection expressions

The collection-expression smoke test was Skip'd because compiling
[1, 2, 3].CountIt() triggered AssertUnderlyingConversionsCheckedRecursive
in Binder_Conversions.cs. The receiver's BoundUnconvertedCollectionExpression
was being destructively converted by ReplaceTypeOrValueReceiver's default
branch (which calls BindToNaturalType -> BindCollectionExpressionForErrorRecovery)
before reaching coerceArgument, so the line-274 early-return that handles
collection-expression conversions correctly never fired.

Skip ReplaceTypeOrValueReceiver in BindInvocationExpressionContinued when the
receiver is typeless and the call is invokedAsExtensionMethod. Safe because
BindToNaturalType is a no-op for typed receivers (NeedsToBeConverted early
return) and ReplaceTypeOrValueReceiver's named purpose - replacing
TypeOrValueExpression or QueryClause - never applies here since both wrappers
always have a type.

Scope TryBindMemberAccessOnTypelessReceiver's inclusion list to just
UnconvertedCollectionExpression for Phase 1. The other receiver categories
(target-typed new(), conditional/switch with no common arm type, lambda
without natural type, method group, tuple literal with typeless element,
default literal, null literal) will be enabled in their respective Phase 2
area PRs together with their test coverage. Until then, those categories
keep producing their pre-feature diagnostics.

Un-skip CollectionExpression_ClassicExtensionMethod_Executes (now passes).
Skip NullLiteral and Lambda smoke tests with a TODO referencing the relevant
Phase 2 area.

Co-authored-by: Isaac
…eceiver feature

The extension-members-on-typeless-receivers feature changes the meaning of 8
existing tests in CollectionExpressionTests:

* TypeInference_07/08/NullableValueType_ExtensionMethod: [...]ExtensionMethod()
  now succeeds via type inference through the collection-expression conversion,
  instead of producing ERR_CollectionExpressionNoTargetType on the receiver.

* TypeInference_09: [4].AsCollection() now reports the same
  ERR_CantInferMethTypeArgs as the explicit-form call, instead of
  ERR_CollectionExpressionNoTargetType on the receiver.

* MemberAccess_01/02/03/04: [].GetHashCode() (direct '.' on a typeless
  collection-expression receiver) now enters extension-method search and
  reports ERR_NoSuchMember, instead of ERR_CollectionExpressionNoTargetType.
  The '?.' and '[]' forms are out of scope per the proposal and continue to
  produce ERR_CollectionExpressionNoTargetType.

A follow-up PR (Phase 2 CollectionExpression area) should add C#14 variants
of these tests so both pre-feature and post-feature behavior is documented.

Co-authored-by: Isaac
…receiver feature

Eighteen consolidated tests covering classic extension methods invoked on a
collection-expression receiver. Confidence-focused, not exhaustive. Categories:

  - Target type variety: array, List<T>, IEnumerable<T>, IReadOnlyList<T>.
  - Edge cases: empty collection (T cannot be inferred), spread, nested
    collection expressions, string element type.
  - Generic inference: receiver and additional arguments, struct constraint
    (satisfied and violated), two-parameter generic.
  - Negative: receiver-type-not-constructible, no-extension-in-scope.
  - Overload resolution: ambiguity between equally-applicable candidates,
    preference for the more specific candidate (T[] over IEnumerable<T>).
  - Chained: typeless-receiver call followed by typed-receiver call.
  - Argument forwarding: positional and named additional arguments.

Three tests use CompileAndVerify with expectedOutput to confirm the lowered
code executes correctly, not just that it binds.

Co-authored-by: Isaac
…l modern path is wired up)

Probed whether modern (C# 14) extension members declared inside an
`extension(T) { ... }` block bind on a typeless collection-expression
receiver. They do not: the classic path runs through BindInstanceMemberAccess
which TryBindMemberAccessOnTypelessReceiver routes to, but the modern path
runs through GetExtensionMemberAccess, which does not yet honor a typeless
receiver.

Two tests document the current ERR_CollectionExpressionNoTargetType behavior
so the file is green. Both have a TODO referencing the future flip to
asserting successful binding once the modern path is enabled.

Co-authored-by: Isaac
Phase 1 only relaxed the receiver-conversion path for classic extension
methods. C#14 modern extension members declared inside an extension(T) { ... }
block use two adjacent code paths that also called ReplaceTypeOrValueReceiver
on the receiver, which destructively converts typeless forms (BoundUnconverted
CollectionExpression, etc.) into error-recovery wrappers and reports
ERR_CollectionExpressionNoTargetType:

  1. BindInvocationExpressionContinued (modern method invocation): line 1242.
     Phase 1 gated the relaxation on invokedAsExtensionMethod, but the modern
     method path resets invokedAsExtensionMethod to false above, so the
     relaxation didn't apply.

  2. GetExtensionMemberAccess (modern property and indexer access): line 8265.
     Same shape, no analogous relaxation.

For both call sites: skip ReplaceTypeOrValueReceiver when the receiver has no
type. The downstream conversion is then applied by CheckAndConvertExtensionReceiver
against the extension's declared receiver parameter, just like for typed
receivers. ReplaceTypeOrValueReceiver's named purpose (replacing
TypeOrValueExpression or unwrapping QueryClause) does not apply since both
of those wrappers always have a type.

Flips the two placeholder tests in
CollectionExpression_ModernExtensionMethod_Tests.cs (added in the prior PR in
this stack) from documenting the limitation to asserting successful binding
(CompileAndVerify with expectedOutput "6" and "3").

All eight .NET Core CSharp test projects pass with zero failures.

Co-authored-by: Isaac
…-receiver feature

Eight tests covering C#14 modern extension properties accessed on a
collection-expression receiver. Confidence-focused, not exhaustive.

  - Basic property access on IEnumerable<T> with execution.
  - Generic extension property with execution (Count<T>).
  - Get-only property on IReadOnlyList<T>, on int[], on IEnumerable<string>.
  - Chained property access (typeless receiver -> typed result -> typed access).
  - Spread elements feeding the collection-expression receiver.
  - Negative: no candidate in scope reports ERR_NoSuchMember.

Six tests use CompileAndVerify with expectedOutput to confirm the lowered code
executes correctly, not just that it binds.

A ninth test for empty-collection + generic extension property is deferred:
it triggers a Debug.Fail in OverloadResolutionResult.TypeInferenceFailed
during property-resolution error reporting (non-serializable argument). That
diagnostic-reporting bug also affects typed receivers in the same scenario,
so it's tracked separately and out of scope for this PR.

Co-authored-by: Isaac
… pins)

Modern extension indexers and the typeless-receivers feature don't currently
meet on either side:

  - Instance indexers are not allowed inside extension(T) { ... } blocks
    (CS9282 ERR_ExtensionDisallowsMember). So no instance indexer can be
    reached through the typeless-receiver path.

  - Element access via expr[args] on a typeless receiver is explicitly out
    of scope per the proposal: dot-form only.

Two tests pin those facts so a future change to either makes the failure
surface and we can revisit.

Co-authored-by: Isaac
Adds BoundKind.UnboundLambda to TryBindMemberAccessOnTypelessReceiver's
inclusion list so that member access on a lambda whose natural type cannot
be inferred (per the C#10 / C#13 natural-type rules) routes through the
typeless-extension-receiver path. The headline scenario is the Memoize
example from the proposal:

  (x => x * 2).Apply(5)

where Apply is an extension method on the inferred delegate type.

Un-skips the corresponding smoke test (Lambda_ClassicExtensionMethod_Executes).
The eight .NET Core CSharp test projects pass with zero failures - existing
tests of "(arg => ...).ToString()"-style errors continue to pass because
lambdas whose member access is downstream of a target-typed assignment do
not enter this path as BoundUnboundLambda.

Co-authored-by: Isaac
Nine consolidated tests covering classic extension methods invoked on a
lambda (without natural type) receiver. Confidence-focused, not exhaustive.

  - Func<int,int>, Action, Func<int,int,int> targets with execution.
  - Lambda with explicit typed parameter still routes through the typeless
    path until the extension binds it to a delegate.
  - Generic extension Apply<T>(Func<T,T>) inferred from the lambda body.
  - Negative: no candidate in scope reports ERR_NoSuchMember on
    'lambda expression'.
  - Overload resolution: ambiguity, preference for the more specific
    candidate (Func<int,int> over Delegate).
  - Headline: Memoize over a lambda, returning a memoized Func<int,int>.

Six tests use CompileAndVerify with expectedOutput.

Co-authored-by: Isaac
Four consolidated tests covering C#14 modern extension methods (declared
inside extension(T) { ... } blocks) invoked on a lambda receiver.

  - extension(Func<int,int>) Apply with execution.
  - Generic extension<T>(Func<T,T>) Apply with execution.
  - extension(Action) RunIt with execution.
  - Negative: no candidate in scope reports ERR_NoSuchMember.

Three tests use CompileAndVerify with expectedOutput.

Co-authored-by: Isaac
Three tests covering modern extension properties accessed on a lambda
receiver: Func<int,int>, generic Func<T,T>, and a negative no-candidate
case. Two tests use CompileAndVerify with expectedOutput.

Co-authored-by: Isaac
Same pattern as CollectionExpression_ModernExtensionIndexer_Tests:

  - Instance indexers in an extension(T) { ... } block on Func<int,int>
    report CS9282 ERR_ExtensionDisallowsMember.
  - `(x => x)[0]` element access on a lambda is rejected with
    ERR_BadIndexLHS; the typeless-receivers proposal does not extend `[]`.

Closes the Lambda area (4 of 4 shape PRs).

Co-authored-by: Isaac
Adds BoundKind.MethodGroup (filtered to non-empty / non-errored method groups)
to TryBindMemberAccessOnTypelessReceiver's inclusion list. This enables the
Memoize-on-method-group example from the proposal:

  Square.RunIt(5)  // where RunIt is an extension on Func<int,int>

The filter (Methods.Length > 0 && LookupError is null) excludes empty /
errored method groups produced when an inaccessible nested-type lookup or a
similar failed lookup falls through to extension-method search. Without
this filter, scenarios such as `I1.T4.B` where T4 is an inaccessible enum
would produce ERR_FeatureInPreview instead of preserving ERR_BadAccess.

All eight .NET Core CSharp test projects pass with zero failures.

Co-authored-by: Isaac
…feature

Six consolidated tests covering classic extension methods invoked on a
method-group receiver, the proposal's Memoize headline scenario.

  - Static method group as receiver of an extension on Func<int,int>.
  - Instance method group as receiver.
  - Generic extension Apply<T>(Func<T,T>) inferred from the method group.
  - Memoize over a method group, returning a memoized Func<int,int>.
  - Overloaded method group (no natural type) target-typed against the
    extension's first parameter type.
  - Negative: no candidate in scope reports ERR_NoSuchMember on
    'method group'.

Five tests use CompileAndVerify with expectedOutput.

Co-authored-by: Isaac
…eature

Three tests covering modern (C#14) extension methods invoked on a method-group
receiver:

  - extension(Func<int,int>) RunIt with execution.
  - extension<T>(Func<T,T>) Apply with execution.
  - Negative: no candidate in scope reports ERR_NoSuchMember.

Two tests use CompileAndVerify with expectedOutput.

Co-authored-by: Isaac
… feature

Two tests covering modern extension properties accessed on a method-group
receiver: one positive (extension(Func<int,int>) Property with execution),
one negative (no candidate in scope). A generic-property test is deferred
with TODO referencing the same diagnostic-reporting Debug.Fail seen in
the earlier empty-collection + generic-property scenario.

Co-authored-by: Isaac
Two pin tests, same pattern as CollectionExpression / Lambda indexer PRs:
instance indexers in extension(T) blocks are not allowed (CS9282), and
element access via [] on a method-group receiver continues to report
ERR_BadIndexLHS.

Closes the MethodGroup area (4 of 4 shape PRs).

Co-authored-by: Isaac
Adds BoundKind.UnconvertedObjectCreationExpression to TryBindMemberAccessOnTypelessReceiver's
inclusion list so that member access on `new()` / `new(args)` routes through the typeless-
extension-receiver path. Headline scenario:

  new(arg).SomeExtension()

where SomeExtension is an extension on the inferred receiver type.

All eight .NET Core CSharp test projects pass with zero failures.

This is the support PR for the NewExpression area; the four shape PRs follow.

Co-authored-by: Isaac
…r feature

Four tests covering classic extension methods invoked on a target-typed
new() / new(args) receiver:

  - new() with parameterless ctor + extension on the type, with execution.
  - new(arg) with single-arg ctor, with execution.
  - new(a, b) with multi-arg ctor + ctor overload resolution, with execution.
  - Negative: no candidate in scope reports ERR_NoSuchMember on 'new()'.

Three tests use CompileAndVerify with expectedOutput.

Co-authored-by: Isaac
… feature

Three tests covering modern (C#14) extension methods invoked on a target-typed
new() / new(args) receiver. Two execute, one negative.

Co-authored-by: Isaac
…er feature

Three tests covering modern extension properties accessed on a target-typed
new() / new(args) receiver. Two execute, one negative.

Co-authored-by: Isaac
Two pin tests, same pattern as previous indexer PRs. Closes the NewExpression area.

Co-authored-by: Isaac
Adds BoundLiteral with constant-value null to TryBindMemberAccessOnTypelessReceiver's
inclusion list. Un-skips the corresponding smoke test (NullLiteral_ClassicExtensionMethod_Executes).

All eight .NET Core CSharp test projects pass with zero failures.

Co-authored-by: Isaac
…feature

Five tests covering classic extension methods invoked on a null-literal receiver:
null to reference type, null to nullable value type, overload resolution prefers
the more specific candidate, ambiguity between two unrelated reference types,
no candidate in scope.

Three tests use CompileAndVerify with expectedOutput.

Co-authored-by: Isaac
Cyrus Najmabadi added 23 commits April 26, 2026 14:28
…ssion/Tests' into extension-members-on-typeless-receivers/Lambda/Support
… into extension-members-on-typeless-receivers/Lambda/Tests
…nto extension-members-on-typeless-receivers/MethodGroup/Support
…port' into extension-members-on-typeless-receivers/MethodGroup/Tests
…ts' into extension-members-on-typeless-receivers/NewExpression/Support
…upport' into extension-members-on-typeless-receivers/NewExpression/Tests
…ests' into extension-members-on-typeless-receivers/NullLiteral/Support
…port' into extension-members-on-typeless-receivers/NullLiteral/Tests
…ts' into extension-members-on-typeless-receivers/DefaultLiteral/Support
…Support' into extension-members-on-typeless-receivers/DefaultLiteral/Tests
When the speculative-binding redesign (in /product) made the typeless-
receiver helper only engage when an extension candidate is in scope,
these tests' "no extension found" assertions started reporting the
legacy ERR_BadUnaryOp from the UnboundLambda rejection in
BindMemberAccessWithBoundLeft instead of the new ERR_NoSuchMember.

Updated three tests across Classic / ModernMethod / ModernProperty.

Co-authored-by: Isaac
…nto extension-members-on-typeless-receivers/MethodGroup/Support
…port' into extension-members-on-typeless-receivers/MethodGroup/Tests
…llback

Three method-group "no extension found" tests now expect the legacy
ERR_BadSKunknown ('method is not valid in the given context') instead of
ERR_NoSuchMember, since the speculative-binding redesign falls back to
the legacy path when no extension candidate is in scope.

Co-authored-by: Isaac
…ts' into extension-members-on-typeless-receivers/NewExpression/Support
…upport' into extension-members-on-typeless-receivers/NewExpression/Tests
…fallback

Three new()-receiver "no extension found" tests now expect the legacy
ERR_ImplicitObjectCreationNoTargetType instead of ERR_NoSuchMember.

Co-authored-by: Isaac
…ests' into extension-members-on-typeless-receivers/NullLiteral/Support
…port' into extension-members-on-typeless-receivers/NullLiteral/Tests
…llback

Three null-receiver "no extension found" tests now expect the legacy
ERR_BadUnaryOp on '<null>' instead of ERR_NoSuchMember.

Co-authored-by: Isaac
…ts' into extension-members-on-typeless-receivers/DefaultLiteral/Support
…Support' into extension-members-on-typeless-receivers/DefaultLiteral/Tests
… fallback

Three default-receiver "no extension found" tests now expect the legacy
ERR_DefaultLiteralNoTargetType instead of ERR_NoSuchMember.

Co-authored-by: Isaac
@CyrusNajmabadi CyrusNajmabadi changed the title Add tests for extension members on typeless default-literal receivers [typeless extension receivers] Tests for default-literal receivers Apr 26, 2026
@CyrusNajmabadi CyrusNajmabadi changed the base branch from main to features/extension-members-on-typeless-receivers April 27, 2026 16:42
@jcouv
Copy link
Copy Markdown
Member

jcouv commented Apr 27, 2026

Test plan: #83428
(created by new-compiler-feature skill)

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

Labels

Area-Compilers Community The pull request was submitted by a contributor who is not a Microsoft employee. Feature - Extension members on typeless receivers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants