Skip to content

Commit 9191cc2

Browse files
Merge pull request #9229 from dotnet/madst-extensions
Describe scoping rules for extension declaration type parameters and receiver parameter; describe rules for extension operators.
2 parents e2e62a4 + 9baf800 commit 9191cc2

File tree

3 files changed

+236
-1
lines changed

3 files changed

+236
-1
lines changed

meetings/working-groups/extensions/disambiguation-syntax-examples.md

Lines changed: 131 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ var repetition = new IEnumerable<int>(first, 10);
4848
// Static extension members
4949
var range = IEnumerable<int>.(MyExtensions.Range)(0, 10);
5050
var empty = IEnumerable<int>.(MyExtensions.Empty);
51-
range (MyExtensions.+=) 5;
51+
range (MyExtensions.+=) 5; /* or */ (MyExtensions.+)(range, 5);
5252
ReadOnlySpan<int> span = (MyExtensions.implicit)(range);
5353

5454
// Instance extension members
@@ -200,3 +200,133 @@ var isEmpty = (query using MyExtensions).IsEmpty;
200200
var first = (query using MyExtensions)[0];
201201
var repetition = new (IEnumerable<int> using MyExtensions)(first, 10);
202202
```
203+
204+
## Speakable lowering
205+
206+
``` c#
207+
// Static extension members
208+
var range = MyExtensions.Range(0, 10);
209+
var empty = MyExtensions.Empty;
210+
range = MyExtensions.op_addition(range, 5);
211+
ReadOnlySpan<int> span = MyExtensions.op_implicit(range); // Need target typing?
212+
213+
// Instance extension members
214+
var query = MyExtensions.Where(range, i => i < 10);
215+
var isEmpty = MyExtensions.get_IsEmpty(query);
216+
var first = MyExtensions.get_Item(query, 0);
217+
var repetition = MyExtensions.__ctor_IEnumerable<int>(first, 10);
218+
```
219+
220+
## Using clauses as statements
221+
222+
``` c#
223+
using static MyExtensions
224+
{
225+
// Static extension members
226+
var range = IEnumerable<int>.Range(0, 10);
227+
var empty = IEnumerable<int>.Empty;
228+
range += 5;
229+
ReadOnlySpan<int> span = range;
230+
231+
// Instance extension members
232+
var query = range.Where(i => i < 10);
233+
var isEmpty = query.IsEmpty;
234+
var first = query[0];
235+
var repetition = new IEnumerable<int>(first, 10);
236+
}
237+
```
238+
239+
- Doesn't address bypassing instance member
240+
241+
242+
``` c#
243+
// Static extension members
244+
var range = MyExtensions.(IEnumerable<int>).Range(0, 10);
245+
var empty = MyExtensions.(IEnumerable<int>).Empty;
246+
MyExtensions.(range) += 5;
247+
ReadOnlySpan<int> span = MyExtensions.(range);
248+
249+
// Instance extension members
250+
var query = MyExtensions.(range).Where(i => i < 10);
251+
var isEmpty = MyExtensions.(query).IsEmpty;
252+
var first = MyExtensions.(query)[0];
253+
var repetition = new MyExtensions.(IEnumerable<int>)(first, 10);
254+
```
255+
256+
257+
``` c#
258+
// Static extension members
259+
var range = IEnumerable<int>.(MyExtensions).Range(0, 10);
260+
var empty = IEnumerable<int>.(MyExtensions).Empty;
261+
range.(MyExtensions) += 5;
262+
ReadOnlySpan<int> span = range.(MyExtensions);
263+
264+
// Instance extension members
265+
var query = range.(MyExtensions).Where(i => i < 10);
266+
var isEmpty = query.(MyExtensions).IsEmpty;
267+
var first = query.(MyExtensions)[0];
268+
var repetition = new IEnumerable<int>.(MyExtensions)(first, 10);
269+
```
270+
271+
272+
``` c#
273+
// Static extension members
274+
var range = IEnumerable<int>.as(MyExtensions).Range(0, 10);
275+
var empty = IEnumerable<int>.as(MyExtensions).Empty;
276+
range.as(MyExtensions) += 5;
277+
ReadOnlySpan<int> span = range.as(MyExtensions);
278+
279+
// Instance extension members
280+
var query = range.as(MyExtensions).Where(i => i < 10);
281+
var isEmpty = query.as(MyExtensions).IsEmpty;
282+
var first = query.as(MyExtensions)[0];
283+
var repetition = new IEnumerable<int>.as(MyExtensions)(first, 10);
284+
```
285+
286+
287+
``` c#
288+
// Static extension members
289+
var range = IEnumerable<int>.MyExtensions.Range(0, 10);
290+
var empty = IEnumerable<int>.MyExtensions.Empty;
291+
range.MyExtensions += 5;
292+
ReadOnlySpan<int> span = range.MyExtensions;
293+
294+
// Instance extension members
295+
var query = range.MyExtensions.Where(i => i < 10);
296+
var isEmpty = query.MyExtensions.IsEmpty;
297+
var first = query.MyExtensions[0];
298+
var repetition = new IEnumerable<int>.MyExtensions(first, 10);
299+
```
300+
- Doesn't allow giving the full name of `MyExtensions`
301+
302+
303+
``` c#
304+
// Static extension members
305+
var range = IEnumerable<int>.in(MyExtensions).Range(0, 10);
306+
var empty = IEnumerable<int>.in(MyExtensions).Empty;
307+
range.in(MyExtensions) += 5;
308+
ReadOnlySpan<int> span = range.in(MyExtensions);
309+
310+
// Instance extension members
311+
var query = range.in(MyExtensions).Where(i => i < 10);
312+
var isEmpty = query.in(MyExtensions).IsEmpty;
313+
var first = query.in(MyExtensions)[0];
314+
var repetition = new IEnumerable<int>.in(MyExtensions)(first, 10);
315+
```
316+
317+
318+
319+
``` c#
320+
// Static extension members
321+
var range = IEnumerable<int>.at(MyExtensions).Range(0, 10);
322+
var empty = IEnumerable<int>.at(MyExtensions).Empty;
323+
range.at(MyExtensions) += 5;
324+
ReadOnlySpan<int> span = range.at(MyExtensions);
325+
326+
// Instance extension members
327+
var query = range.at(MyExtensions).Where(i => i < 10);
328+
var isEmpty = query.at(MyExtensions).IsEmpty;
329+
var first = query.at(MyExtensions)[0];
330+
var repetition = new IEnumerable<int>.at(MyExtensions)(first, 10);
331+
```
332+
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Extension operators
2+
3+
***Note:*** *Does not cover user-defined implicit and explicit conversion operators, which are not yet designed or planned.*
4+
5+
## Declaration
6+
Like all extension members, extension operators are declared within an extension block:
7+
8+
``` c#
9+
public static class Operators
10+
{
11+
extension<TElement>(TElement[] source) where TElement : INumber<TElement>
12+
{
13+
public static TElement[] operator *(TElement[] vector, TElement scalar) { ... }
14+
public static TElement[] operator *(TElement scalar, TElement[] vector) { ... }
15+
public void operator *=(TElement scalar) { ... }
16+
}
17+
}
18+
```
19+
20+
Extension operator declarations generally follow the rules for non-extension [user-defined operators in the Standard](https://github.com/dotnet/csharpstandard/blob/standard-v7/standard/classes.md#1510-operators) as well as the soon-to-be-added [user-defined compound assignment operators](https://github.com/dotnet/csharplang/blob/main/proposals/user-defined-compound-assignment.md).
21+
22+
Generally speaking, the rules pertaining to the *containing type* of the declaration instead apply to the *extended type*.
23+
24+
A static extension operator for the extended type `T` must take at least one parameter of type `S` or `S?`, where `S` and `T` are identity convertible.
25+
26+
For operators that require pair-wise declaration, the two declarations are allowed to occur in separate extension blocks for extended types `S` and `T` respectively, as long as `S` and `T` are identity convertible and the extension blocks occur in the same static class.
27+
28+
Like other extension members, extension operators cannot use the `abstract`, `virtual`, `override` or `sealed` modifiers.
29+
30+
An extension operator may not have the same signature as a predefined operator.
31+
32+
``` c#
33+
public static class Operators
34+
{
35+
extension(int[])
36+
{
37+
public static int[] operator +(int[] vector, int scalar) { ... } // OK; parameter and extended type agree
38+
public static int operator +(int scalar1, int scalar2) { ... } // ERROR: extended type not used as parameter type
39+
// and ERROR: same signature as predefined +(int, int)
40+
public static bool operator ==(int[] vector1, int[] vector2){ ... } // ERROR: '!=' declaration missing
41+
public static bool operator <(int[] vector1, int[] vector2){ ... } // OK: `>` is declared below
42+
}
43+
extension(int[])
44+
{
45+
public static bool operator >(int[] vector1, int[] vector2){ ... } // OK: `<` is declared above
46+
}
47+
}
48+
```
49+
50+
**Open question:** Should we disallow user-defined extension operators where the types of all parameters and receivers are type parameters? Otherwise specific instantiations could have the same signature as a predefined operator.
51+
52+
## Overload resolution
53+
54+
Like other extension members, extension operators are only considered if no applicable predefined or non-extension user-defined operators were found. The search then proceeds outwards scope by scope, starting with the innermost namespace within which the operator application occurs, until at least one applicable extension operator declaration is found. For compound assignment operators, this involves looking for first user-defined compound assignment operators and then, if none found, non-assignment operators, before moving on to the next scope.
55+
56+
At each scope, overload resolution works the same as other extension members:
57+
- The set of operator declarations for the given name (i.e. operator) is determined
58+
- Type inference is attempted for each, based on operand expressions
59+
- Declarations that fail type inference are removed
60+
- Declarations that are not applicable to the operands are removed
61+
- If there are no remaining candidates, proceed to the enclosing scope (or, if looking for compound-assignment operators, the corresponding simple operator)
62+
- Applying overload resolution between the candidate, select the unique best candidate, if it exists
63+
- If no unique best candidate can be found, overload resolution fails with an ambiguity error.
64+
65+
Using `*` and `*=` declarations above:
66+
67+
``` c#
68+
int[] numbers = { 1, 2, 3 };
69+
70+
var i = 2 * 3; // predefined operator *(int, int)
71+
var v = numbers * 4; // extension operator *(int[], int)
72+
v *= 5; // extension operator *=(int)
73+
```

proposals/extensions.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,38 @@ receiver_parameter // add
5252
Extension declarations shall only be declared in non-generic, non-nested static classes.
5353
It is an error for a type to be named `extension`.
5454

55+
### Scoping rules
56+
57+
The type parameters and receiver parameter of an extension declaration are in scope within the body of the extension declaration. It is an error to refer to the receiver parameter from within a static member, except within a `nameof` expression. It is an error for members to declare type parameters or parameters (as well as local variables and local functions directly within the member body) with the same name as a type parameter or receiver parameter of the extension declaration.
58+
59+
``` c#
60+
public static class E
61+
{
62+
extension<T>(T[] ts)
63+
{
64+
public bool M1(T t) => ts.Contains(t); // `T` and `ts` are in scope
65+
public static bool M2(T t) => ts.Contains(t); // Error: Cannot refer to `ts` from static context
66+
public void M3(int T, string ts) { } // Error: Cannot reuse names `T` and `ts`
67+
public void M4<T, ts>(string s) { } // Error: Cannot reuse names `T` and `ts`
68+
}
69+
}
70+
```
71+
72+
It is not an error for the members themselves to have the same name as the type parameters or receiver parameter of the enclosing extension declaration. Member names are not directly found in a simple name lookup from within the extension declaration; lookup will thus find the type parameter or receiver parameter of that name, rather than the member.
73+
74+
Members do give rise to static methods being declared directly on the enclosing static class, and those can be found via simple name lookup; however, an extension declaration type parameter or receiver parameter of the same name will be found first.
75+
76+
``` c#
77+
public static class E
78+
{
79+
extension<T>(T[] ts)
80+
{
81+
public void T() { M(ts); } // Generated static method M<T>(T[]) is found
82+
public void M() { T(ts); } // Error: T is a type parameter
83+
}
84+
}
85+
```
86+
5587
### Static classes as extension containers
5688

5789
Extensions are declared inside top-level non-generic static classes, just like extension methods today,

0 commit comments

Comments
 (0)