Skip to content

Commit a3918bf

Browse files
authored
Merge pull request #41847 from dotnet/features/local-function-attributes
Features/local function attributes
2 parents b494eec + 081a7a5 commit a3918bf

File tree

120 files changed

+18139
-2939
lines changed

Some content is hidden

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

120 files changed

+18139
-2939
lines changed

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

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,23 @@ internal virtual ImmutableHashSet<Symbol> LockedOrDisposedVariables
3939
/// </remarks>
4040
public virtual BoundStatement BindStatement(StatementSyntax node, DiagnosticBag diagnostics)
4141
{
42+
if (node.AttributeLists.Count > 0)
43+
{
44+
var attributeList = node.AttributeLists[0];
45+
46+
// Currently, attributes are only allowed on local-functions.
47+
if (node.Kind() == SyntaxKind.LocalFunctionStatement)
48+
{
49+
CheckFeatureAvailability(attributeList, MessageID.IDS_FeatureLocalFunctionAttributes, diagnostics);
50+
}
51+
else if (node.Kind() != SyntaxKind.Block)
52+
{
53+
// Don't explicitly error here for blocks. Some codepaths bypass BindStatement
54+
// to directly call BindBlock.
55+
Error(diagnostics, ErrorCode.ERR_AttributesNotAllowed, attributeList);
56+
}
57+
}
58+
4259
Debug.Assert(node != null);
4360
BoundStatement result;
4461
switch (node.Kind())
@@ -554,13 +571,19 @@ private BoundStatement BindLocalFunctionStatement(LocalFunctionStatementSyntax n
554571
{
555572
expressionBody = runAnalysis(BindExpressionBodyAsBlock(node.ExpressionBody, diagnostics), diagnostics);
556573
}
557-
else
574+
else if (!hasErrors && (!localSymbol.IsExtern || !localSymbol.IsStatic))
558575
{
559576
hasErrors = true;
560577
diagnostics.Add(ErrorCode.ERR_LocalFunctionMissingBody, localSymbol.Locations[0], localSymbol);
561578
}
562579

563-
Debug.Assert(blockBody != null || expressionBody != null || hasErrors);
580+
if (!hasErrors && (blockBody != null || expressionBody != null) && localSymbol.IsExtern)
581+
{
582+
hasErrors = true;
583+
diagnostics.Add(ErrorCode.ERR_ExternHasBody, localSymbol.Locations[0], localSymbol);
584+
}
585+
586+
Debug.Assert(blockBody != null || expressionBody != null || (localSymbol.IsExtern && localSymbol.IsStatic) || hasErrors);
564587

565588
localSymbol.GetDeclarationDiagnostics(diagnostics);
566589

@@ -1673,6 +1696,11 @@ internal virtual BoundBlock BindEmbeddedBlock(BlockSyntax node, DiagnosticBag di
16731696

16741697
private BoundBlock BindBlock(BlockSyntax node, DiagnosticBag diagnostics)
16751698
{
1699+
if (node.AttributeLists.Count > 0)
1700+
{
1701+
Error(diagnostics, ErrorCode.ERR_AttributesNotAllowed, node.AttributeLists[0]);
1702+
}
1703+
16761704
var binder = GetBinder(node);
16771705
Debug.Assert(binder != null);
16781706

src/Compilers/CSharp/Portable/BoundTree/BoundLocalFunctionStatement.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33
// See the LICENSE file in the project root for more information.
44

5+
#nullable enable
6+
57
using System;
68
using System.Collections.Generic;
79
using System.Text;
@@ -10,6 +12,6 @@ namespace Microsoft.CodeAnalysis.CSharp
1012
{
1113
internal partial class BoundLocalFunctionStatement
1214
{
13-
public BoundBlock Body { get => BlockBody ?? ExpressionBody; }
15+
public BoundBlock? Body { get => BlockBody ?? ExpressionBody; }
1416
}
1517
}

src/Compilers/CSharp/Portable/BoundTree/UnboundLambda.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -611,7 +611,7 @@ private BoundLambda ReallyBind(NamedTypeSymbol delegateType)
611611
if (IsAsync)
612612
{
613613
Debug.Assert(lambdaSymbol.IsAsync);
614-
SourceOrdinaryMethodSymbol.ReportAsyncParameterErrors(lambdaSymbol.Parameters, diagnostics, lambdaSymbol.DiagnosticLocation);
614+
lambdaSymbol.ReportAsyncParameterErrors(diagnostics, lambdaSymbol.DiagnosticLocation);
615615
}
616616

617617
var result = new BoundLambda(_unboundLambda.Syntax, _unboundLambda, block, diagnostics.ToReadOnlyAndFree(), lambdaBodyBinder, delegateType, inferredReturnType: default)

src/Compilers/CSharp/Portable/CSharpResources.resx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,12 @@
126126
<data name="IDS_FeatureSwitchExpression" xml:space="preserve">
127127
<value>&lt;switch expression&gt;</value>
128128
</data>
129+
<data name="IDS_FeatureLocalFunctionAttributes" xml:space="preserve">
130+
<value>local function attributes</value>
131+
</data>
132+
<data name="IDS_FeatureExternLocalFunctions" xml:space="preserve">
133+
<value>extern local functions</value>
134+
</data>
129135
<data name="IDS_RELATEDERROR" xml:space="preserve">
130136
<value>(Location of symbol related to previous error)</value>
131137
</data>
@@ -1023,6 +1029,9 @@
10231029
<data name="ERR_ConditionalOnOverride" xml:space="preserve">
10241030
<value>The Conditional attribute is not valid on '{0}' because it is an override method</value>
10251031
</data>
1032+
<data name="ERR_ConditionalOnLocalFunction" xml:space="preserve">
1033+
<value>Local function '{0}' must be 'static' in order to use the Conditional attribute</value>
1034+
</data>
10261035
<data name="ERR_PointerInAsOrIs" xml:space="preserve">
10271036
<value>Neither 'is' nor 'as' is valid on pointer types</value>
10281037
</data>
@@ -1713,7 +1722,7 @@ If such a class is used as a base class and if the deriving class defines a dest
17131722
<value>Cannot update '{0}'; attribute '{1}' is missing.</value>
17141723
</data>
17151724
<data name="ERR_DllImportOnGenericMethod" xml:space="preserve">
1716-
<value>The DllImport attribute cannot be applied to a method that is generic or contained in a generic type.</value>
1725+
<value>The DllImport attribute cannot be applied to a method that is generic or contained in a generic method or type.</value>
17171726
</data>
17181727
<data name="ERR_FieldCantBeRefAny" xml:space="preserve">
17191728
<value>Field or property cannot be of type '{0}'</value>
@@ -5570,7 +5579,7 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
55705579
<value>Local function is declared but never used</value>
55715580
</data>
55725581
<data name="ERR_LocalFunctionMissingBody" xml:space="preserve">
5573-
<value>'{0}' is a local function and must therefore always have a body.</value>
5582+
<value>Local function '{0}' must declare a body because it is not marked 'static extern'.</value>
55745583
</data>
55755584
<data name="ERR_InvalidDebugInfo" xml:space="preserve">
55765585
<value>Unable to read debug information of method '{0}' (token 0x{1:X8}) from assembly '{2}'</value>

src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1638,8 +1638,7 @@ private static BoundBlock BindMethodBody(MethodSymbol method, TypeCompilationSta
16381638

16391639
BoundBlock body;
16401640

1641-
var sourceMethod = method as SourceMemberMethodSymbol;
1642-
if ((object)sourceMethod != null)
1641+
if (method is SourceMemberMethodSymbol sourceMethod)
16431642
{
16441643
CSharpSyntaxNode syntaxNode = sourceMethod.SyntaxNode;
16451644
var constructorSyntax = syntaxNode as ConstructorDeclarationSyntax;
@@ -1656,20 +1655,8 @@ private static BoundBlock BindMethodBody(MethodSymbol method, TypeCompilationSta
16561655

16571656
ExecutableCodeBinder bodyBinder = sourceMethod.TryGetBodyBinder();
16581657

1659-
if (sourceMethod.IsExtern)
1658+
if (sourceMethod.IsExtern || sourceMethod.IsDefaultValueTypeConstructor())
16601659
{
1661-
if (bodyBinder == null)
1662-
{
1663-
// Generate warnings only if we are not generating ERR_ExternHasBody or ERR_ExternHasConstructorInitializer errors
1664-
GenerateExternalMethodWarnings(sourceMethod, diagnostics);
1665-
}
1666-
1667-
return null;
1668-
}
1669-
1670-
if (sourceMethod.IsDefaultValueTypeConstructor())
1671-
{
1672-
// No body for default struct constructor.
16731660
return null;
16741661
}
16751662

@@ -1958,18 +1945,6 @@ internal static BoundCall GenerateBaseParameterlessConstructorInitializer(Method
19581945
{ WasCompilerGenerated = true };
19591946
}
19601947

1961-
private static void GenerateExternalMethodWarnings(SourceMemberMethodSymbol methodSymbol, DiagnosticBag diagnostics)
1962-
{
1963-
if (methodSymbol.GetAttributes().IsEmpty && !methodSymbol.ContainingType.IsComImport)
1964-
{
1965-
// external method with no attributes
1966-
var errorCode = (methodSymbol.MethodKind == MethodKind.Constructor || methodSymbol.MethodKind == MethodKind.StaticConstructor) ?
1967-
ErrorCode.WRN_ExternCtorNoImplementation :
1968-
ErrorCode.WRN_ExternMethodNoImplementation;
1969-
diagnostics.Add(errorCode, methodSymbol.Locations[0], methodSymbol);
1970-
}
1971-
}
1972-
19731948
/// <summary>
19741949
/// Returns true if the method is a constructor and has a this() constructor initializer.
19751950
/// </summary>

src/Compilers/CSharp/Portable/Errors/ErrorCode.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1759,6 +1759,8 @@ internal enum ErrorCode
17591759

17601760
ERR_StdInOptionProvidedButConsoleInputIsNotRedirected = 8782,
17611761

1762+
ERR_ConditionalOnLocalFunction = 8783,
1763+
17621764
// Note: you will need to re-generate compiler code after adding warnings (eng\generate-compiler-code.cmd)
17631765
}
17641766
}

src/Compilers/CSharp/Portable/Errors/MessageID.cs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,8 @@ internal enum MessageID
187187
IDS_FeatureSwitchExpression = MessageBase + 12763,
188188
IDS_FeatureAsyncUsing = MessageBase + 12764,
189189
IDS_FeatureLambdaDiscardParameters = MessageBase + 12765,
190+
IDS_FeatureLocalFunctionAttributes = MessageBase + 12766,
191+
IDS_FeatureExternLocalFunctions = MessageBase + 12767,
190192
}
191193

192194
// Message IDs may refer to strings that need to be localized.
@@ -293,8 +295,10 @@ internal static LanguageVersion RequiredVersion(this MessageID feature)
293295
// Checks are in the LanguageParser unless otherwise noted.
294296
switch (feature)
295297
{
296-
// Preview features.
298+
// C# preview features.
297299
case MessageID.IDS_FeatureLambdaDiscardParameters: // semantic check
300+
case MessageID.IDS_FeatureLocalFunctionAttributes: // syntax check
301+
case MessageID.IDS_FeatureExternLocalFunctions: // syntax check
298302
return LanguageVersion.Preview;
299303

300304
// C# 8.0 features.

src/Compilers/CSharp/Portable/FlowAnalysis/NullableWalker.cs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1919,6 +1919,14 @@ protected override void VisitLocalFunctionUse(
19191919
SyntaxNode syntax,
19201920
bool isCall)
19211921
{
1922+
// Do not use this overload in NullableWalker. Use the overload below instead.
1923+
throw ExceptionUtilities.Unreachable;
1924+
}
1925+
1926+
private void VisitLocalFunctionUse(LocalFunctionSymbol symbol)
1927+
{
1928+
Debug.Assert(!IsConditionalState);
1929+
var localFunctionState = GetOrCreateLocalFuncUsages(symbol);
19221930
if (Join(ref localFunctionState.StartingState, ref State) &&
19231931
localFunctionState.Visited)
19241932
{
@@ -3644,10 +3652,6 @@ public override BoundNode VisitCall(BoundCall node)
36443652
// Note: we analyze even omitted calls
36453653
TypeWithState receiverType = VisitCallReceiver(node);
36463654
ReinferMethodAndVisitArguments(node, receiverType);
3647-
if (node.Method?.OriginalDefinition is LocalFunctionSymbol localFunc)
3648-
{
3649-
VisitLocalFunctionUse(localFunc, node.Syntax, isCall: true);
3650-
}
36513655
return null;
36523656
}
36533657

@@ -4084,6 +4088,11 @@ private ImmutableArray<VisitArgumentResult> VisitArguments(
40844088
}
40854089
}
40864090

4091+
if (node is BoundCall { Method: { OriginalDefinition: LocalFunctionSymbol localFunction } })
4092+
{
4093+
VisitLocalFunctionUse(localFunction);
4094+
}
4095+
40874096
if (!node.HasErrors && !parametersOpt.IsDefault)
40884097
{
40894098
// Visit outbound assignments and post-conditions
@@ -5624,7 +5633,7 @@ private TypeWithState VisitConversion(
56245633
{
56255634
if (method?.OriginalDefinition is LocalFunctionSymbol localFunc)
56265635
{
5627-
VisitLocalFunctionUse(localFunc, group.Syntax, isCall: false);
5636+
VisitLocalFunctionUse(localFunc);
56285637
}
56295638
method = CheckMethodGroupReceiverNullability(group, delegateType, method, conversion.IsExtensionMethod);
56305639
}
@@ -6177,7 +6186,7 @@ public override BoundNode VisitDelegateCreationExpression(BoundDelegateCreationE
61776186

61786187
if (node.MethodOpt?.OriginalDefinition is LocalFunctionSymbol localFunc)
61796188
{
6180-
VisitLocalFunctionUse(localFunc, node.Syntax, isCall: true);
6189+
VisitLocalFunctionUse(localFunc);
61816190
}
61826191

61836192
var delegateType = (NamedTypeSymbol)node.Type;

0 commit comments

Comments
 (0)