Extension members on typeless receivers - PR 39: SwitchExpression x ModernExtensionMethod tests#83387
Closed
CyrusNajmabadi wants to merge 46 commits intodotnet:mainfrom
Conversation
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
added 16 commits
April 26, 2026 00:26
…eature Three tests covering modern extension methods invoked on a null-literal receiver: extension(string) OrEmpty, extension(int?) OrZero, no-candidate negative. Co-authored-by: Isaac
… feature Two tests: extension(string) OrEmpty property accessed on null with execution, and a no-candidate negative. Co-authored-by: Isaac
Closes the NullLiteral area (4 of 4 shape PRs). Co-authored-by: Isaac
Adds BoundKind.DefaultLiteral to the inclusion list so 'default.SomeExtension(...)' routes through the typeless-extension-receiver path. All eight .NET Core CSharp test projects pass with zero failures. Co-authored-by: Isaac
…er feature Three tests: default to int extension Plus, default to string extension OrEmpty, no-candidate negative. Co-authored-by: Isaac
Two tests: extension(int) Plus on default, no-candidate negative. Co-authored-by: Isaac
Two tests: extension(int) PlusOne property on default, no-candidate negative. Co-authored-by: Isaac
Closes the DefaultLiteral area (4 of 4 shape PRs). Co-authored-by: Isaac
…sion receiver Adds BoundKind.UnconvertedConditionalOperator to the inclusion list so that '(b ? null : 5).SomeExtension(...)' (conditional with arms of no common type) routes through the typeless-extension-receiver path. All eight .NET Core CSharp test projects pass with zero failures. Co-authored-by: Isaac
Three tests covering classic extension methods invoked on a conditional expression with no common arm type: null-vs-int target-typed to int?, two typeless arms (default vs null) target-typed to string, and a no-candidate negative. Two tests use CompileAndVerify with expectedOutput. Co-authored-by: Isaac
Two tests: extension(int?) OrZero on (b?null:int) with execution, no-candidate negative. Co-authored-by: Isaac
Two tests covering modern extension properties on conditional expressions. Co-authored-by: Isaac
Closes the ConditionalExpression area (4 of 4 shape PRs). Co-authored-by: Isaac
…receiver Adds BoundKind.UnconvertedSwitchExpression to the inclusion list so that switch expressions with no common arm type route through the typeless- extension-receiver path. All eight .NET Core CSharp test projects pass with zero failures. Co-authored-by: Isaac
Two tests covering classic extension methods on switch expressions with no common arm type. One executes, one negative. Co-authored-by: Isaac
Two tests covering modern extension methods on switch expressions. Co-authored-by: Isaac
Contributor
Author
|
Consolidated. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #83386. Two tests covering modern extension methods on switch expressions with no common arm type.
This pull request and its description were written by Isaac.
Microsoft Reviewers: Open in CodeFlow