Skip to content

Commit f72da0b

Browse files
author
Julien Couvreur
authored
Extensions: misc checks on receiver parameter and extension members (#77937)
1 parent 2dc219e commit f72da0b

File tree

55 files changed

+2256
-200
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+2256
-200
lines changed

docs/compilers/CSharp/Compiler Breaking Changes - DotNet 10.md

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -334,26 +334,33 @@ class partial { }
334334

335335
## `extension` treated as a contextual keyword
336336

337-
PROTOTYPE record which version this break is introduced in
338-
337+
***Introduced in Visual Studio 2022 version 17.14.***
339338
Starting with C# 14, the `extension` keyword serves a special purpose in denoting extension containers.
340339
This changes how the compiler interprets certain code constructs.
341340

342341
If you need to use "extension" as an identifier rather than a keyword, escape it with the `@` prefix: `@extension`. This tells the compiler to treat it as a regular identifier instead of a keyword.
343342

344343
The compiler will parse this as an extension container rather than a constructor.
345344
```csharp
346-
class extension
345+
class @extension
347346
{
348347
extension(object o) { } // parsed as an extension container
349348
}
350349
```
351350

352351
The compiler will fail to parse this as a method with return type `extension`.
353352
```csharp
354-
class extension
353+
class @extension
355354
{
356355
extension M() { } // will not compile
357356
}
358357
```
359358

359+
***Introduced in Visual Studio 2022 version 17.15.***
360+
The "extension" identifier may not be used as a type name, so the following will not compile:
361+
```csharp
362+
using extension = ...; // alias may not be named "extension"
363+
class extension { } // type may not be named "extension"
364+
class C<extension> { } // type parameter may not be named "extension"
365+
```
366+

docs/contributing/Compiler Test Plan.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ This document provides guidance for thinking about language interactions and tes
8080
- Readonly members on structs (methods, property/indexer accessors, custom event accessors)
8181
- SkipLocalsInit
8282
- Method override or explicit implementation with `where T : { class, struct, default }`
83+
- `extension` blocks
8384

8485
# Code
8586
- Operators (see Eric's list below)

eng/todo-check.ps1

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ Set-StrictMode -version 2.0
77
$ErrorActionPreference="Stop"
88
if ($env:SYSTEM_PULLREQUEST_TARGETBRANCH -eq "main") {
99
Write-Host "Checking no PROTOTYPE markers in source"
10-
$prototypes = Get-ChildItem -Path src, eng, scripts -Exclude *.dll,*.exe,*.pdb,*.xlf,todo-check.ps1 -Recurse | Select-String -Pattern 'PROTOTYPE' -CaseSensitive -SimpleMatch
10+
$prototypes = Get-ChildItem -Path src, eng, scripts, docs\compilers -Exclude *.dll,*.exe,*.pdb,*.xlf,todo-check.ps1 -Recurse | Select-String -Pattern 'PROTOTYPE' -CaseSensitive -SimpleMatch
1111
if ($prototypes) {
1212
Write-Host "Found PROTOTYPE markers in source:"
1313
Write-Host $prototypes
@@ -16,7 +16,7 @@ if ($env:SYSTEM_PULLREQUEST_TARGETBRANCH -eq "main") {
1616
}
1717

1818
# Verify no TODO2 marker left
19-
$prototypes = Get-ChildItem -Path src, eng, scripts -Exclude *.dll,*.exe,*.pdb,*.xlf,todo-check.ps1 -Recurse | Select-String -Pattern 'TODO2' -CaseSensitive -SimpleMatch
19+
$prototypes = Get-ChildItem -Path src, eng, scripts, docs\compilers -Exclude *.dll,*.exe,*.pdb,*.xlf,todo-check.ps1 -Recurse | Select-String -Pattern 'TODO2' -CaseSensitive -SimpleMatch
2020
if ($prototypes) {
2121
Write-Host "Found TODO2 markers in source:"
2222
Write-Host $prototypes

src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2121,7 +2121,7 @@ private BoundExpression BindNonMethod(SimpleNameSyntax node, Symbol symbol, Bind
21212121
{
21222122
Error(diagnostics, ErrorCode.ERR_InvalidPrimaryConstructorParameterReference, node, parameter);
21232123
}
2124-
else if (parameter.ContainingSymbol is NamedTypeSymbol { IsExtension: true } &&
2124+
else if (parameter.IsExtensionParameter() &&
21252125
(InParameterDefaultValue || InAttributeArgument ||
21262126
this.ContainingMember() is not { Kind: not SymbolKind.NamedType, IsStatic: false } || // We are not in an instance member
21272127
(object)this.ContainingMember().ContainingSymbol != parameter.ContainingSymbol) &&

src/Compilers/CSharp/Portable/Binder/Binder_NameConflicts.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ internal void ValidateParameterNameConflicts(
7474
diagnostics.Add(ErrorCode.ERR_LocalSameNameAsExtensionTypeParameter, GetLocation(p), name);
7575
}
7676
}
77-
else if (p.ContainingSymbol is NamedTypeSymbol { IsExtension: true })
77+
else if (p.IsExtensionParameter())
7878
{
7979
diagnostics.Add(ErrorCode.ERR_TypeParameterSameNameAsExtensionParameter, tp.GetFirstLocationOrNone(), name);
8080
}

src/Compilers/CSharp/Portable/Binder/ExecutableCodeBinder.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,14 +107,21 @@ public static void ValidateIteratorMethod(CSharpCompilation compilation, MethodS
107107
return;
108108
}
109109

110-
foreach (var parameter in iterator.Parameters)
110+
var parameters = !iterator.IsStatic
111+
? iterator.GetParametersIncludingExtensionParameter()
112+
: iterator.Parameters;
113+
114+
foreach (var parameter in parameters)
111115
{
116+
bool isReceiverParameter = parameter.IsExtensionParameter();
112117
if (parameter.RefKind != RefKind.None)
113118
{
114-
diagnostics.Add(ErrorCode.ERR_BadIteratorArgType, parameter.GetFirstLocation());
119+
var location = isReceiverParameter ? iterator.GetFirstLocation() : parameter.GetFirstLocation();
120+
diagnostics.Add(ErrorCode.ERR_BadIteratorArgType, location);
115121
}
116-
else if (parameter.Type.IsPointerOrFunctionPointer())
122+
else if (parameter.Type.IsPointerOrFunctionPointer() && !isReceiverParameter)
117123
{
124+
// We already reported an error elsewhere if the receiver parameter of an extension is a pointer type.
118125
diagnostics.Add(ErrorCode.ERR_UnsafeIteratorArgType, parameter.GetFirstLocation());
119126
}
120127
}

src/Compilers/CSharp/Portable/Binder/Semantics/AccessCheck.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -696,7 +696,9 @@ internal static bool HasInternalAccessTo(this AssemblySymbol fromAssembly, Assem
696696

697697
internal static ErrorCode GetProtectedMemberInSealedTypeError(NamedTypeSymbol containingType)
698698
{
699-
return containingType.TypeKind == TypeKind.Struct ? ErrorCode.ERR_ProtectedInStruct : ErrorCode.WRN_ProtectedInSealed;
699+
return containingType.IsExtension ? ErrorCode.ERR_ProtectedInExtension
700+
: containingType.TypeKind == TypeKind.Struct ? ErrorCode.ERR_ProtectedInStruct
701+
: ErrorCode.WRN_ProtectedInSealed;
700702
}
701703
}
702704
}

src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3878,16 +3878,16 @@ private static EffectiveParameters GetEffectiveParametersInNormalForm<TMember>(
38783878

38793879
hasAnyRefOmittedArgument = false;
38803880

3881-
bool isNewExtensionMember = member.GetIsNewExtensionMember();
3882-
ImmutableArray<ParameterSymbol> parameters = isNewExtensionMember ? GetParametersIncludingReceiver(member) : member.GetParameters();
3881+
ImmutableArray<ParameterSymbol> parameters = member.GetParametersIncludingExtensionParameter();
38833882

38843883
// We simulate an extra parameter for vararg methods
38853884
int parameterCount = parameters.Length + (member.GetIsVararg() ? 1 : 0);
38863885

38873886
if (argumentCount == parameterCount && argToParamMap.IsDefaultOrEmpty)
38883887
{
38893888
bool hasSomeRefKinds = !member.GetParameterRefKinds().IsDefaultOrEmpty;
3890-
if (member.GetIsNewExtensionMember())
3889+
bool isNewExtensionMember = member.GetIsNewExtensionMember();
3890+
if (isNewExtensionMember)
38913891
{
38923892
Debug.Assert(member.ContainingType.ExtensionParameter is not null);
38933893
hasSomeRefKinds |= member.ContainingType.ExtensionParameter.RefKind != RefKind.None;
@@ -4040,7 +4040,7 @@ private static EffectiveParameters GetEffectiveParametersInExpandedForm<TMember>
40404040
var types = ArrayBuilder<TypeWithAnnotations>.GetInstance();
40414041
var refs = ArrayBuilder<RefKind>.GetInstance();
40424042
bool anyRef = false;
4043-
var parameters = member.GetIsNewExtensionMember() ? GetParametersIncludingReceiver(member) : member.GetParameters();
4043+
var parameters = member.GetParametersIncludingExtensionParameter();
40444044
bool hasAnyRefArg = argumentRefKinds.Any();
40454045
hasAnyRefOmittedArgument = false;
40464046
TypeWithAnnotations paramsIterationType = default;

src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolutionResult.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -895,7 +895,7 @@ private static void ReportMissingRequiredParameter(
895895
// to required formal parameter 'y'.
896896

897897
TMember badMember = bad.Member;
898-
ImmutableArray<ParameterSymbol> parameters = badMember.GetIsNewExtensionMember() ? OverloadResolution.GetParametersIncludingReceiver(badMember) : badMember.GetParameters();
898+
ImmutableArray<ParameterSymbol> parameters = badMember.GetParametersIncludingExtensionParameter();
899899
int badParamIndex = bad.Result.BadParameter;
900900
string badParamName;
901901
if (badParamIndex == parameters.Length)
@@ -1115,7 +1115,7 @@ private bool HadBadArguments(
11151115
// as there is no explicit call to Add method.
11161116

11171117
int argumentOffset = arguments.IncludesReceiverAsArgument ? 1 : 0;
1118-
var parameters = method.GetIsNewExtensionMember() ? OverloadResolution.GetParametersIncludingReceiver(method) : method.GetParameters();
1118+
var parameters = method.GetParametersIncludingExtensionParameter();
11191119

11201120
for (int i = argumentOffset; i < parameters.Length; i++)
11211121
{
@@ -1170,7 +1170,7 @@ private static void ReportBadArgumentError(
11701170

11711171
// Early out: if the bad argument is an __arglist parameter then simply report that:
11721172

1173-
var parameters = method.GetIsNewExtensionMember() ? OverloadResolution.GetParametersIncludingReceiver(method) : method.GetParameters();
1173+
var parameters = method.GetParametersIncludingExtensionParameter();
11741174
if (method.GetIsVararg() && parm == parameters.Length)
11751175
{
11761176
// NOTE: No SymbolDistinguisher required, since one of the arguments is "__arglist".

src/Compilers/CSharp/Portable/Binder/Semantics/OverloadResolution/OverloadResolution_ArgsToParameters.cs

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,6 @@ public ImmutableArray<int> ToImmutableArray()
5555
}
5656
}
5757

58-
internal static ImmutableArray<ParameterSymbol> GetParametersIncludingReceiver(Symbol symbol)
59-
{
60-
Debug.Assert(symbol.GetIsNewExtensionMember());
61-
// Tracked by https://github.com/dotnet/roslyn/issues/76130 : consider optimizing
62-
return [symbol.ContainingType.ExtensionParameter, .. symbol.GetParameters()];
63-
}
64-
6558
private static ImmutableArray<TypeWithAnnotations> GetParameterTypesIncludingReceiver(Symbol symbol)
6659
{
6760
Debug.Assert(symbol.GetIsNewExtensionMember());
@@ -78,8 +71,7 @@ private static ArgumentAnalysisResult AnalyzeArguments(
7871
Debug.Assert((object)symbol != null);
7972
Debug.Assert(arguments != null);
8073

81-
bool isNewExtensionMember = symbol.GetIsNewExtensionMember();
82-
ImmutableArray<ParameterSymbol> parameters = isNewExtensionMember ? GetParametersIncludingReceiver(symbol) : symbol.GetParameters();
74+
ImmutableArray<ParameterSymbol> parameters = symbol.GetParametersIncludingExtensionParameter();
8375
bool isVararg = symbol.GetIsVararg();
8476

8577
// The easy out is that we have no named arguments and are in normal form.

0 commit comments

Comments
 (0)