Skip to content

Commit

Permalink
Add native lock feature
Browse files Browse the repository at this point in the history
  • Loading branch information
jjonescz committed Jan 19, 2024
1 parent c02fb4f commit 2cc133d
Show file tree
Hide file tree
Showing 30 changed files with 1,386 additions and 7 deletions.
1 change: 1 addition & 0 deletions docs/Language Feature Status.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ efforts behind them.
| [Roles/Extensions](https://github.com/dotnet/csharplang/issues/5497) | [roles](https://github.com/dotnet/roslyn/tree/features/roles) | [In Progress](https://github.com/dotnet/roslyn/issues/66722) | [jcouv](https://github.com/jcouv) | [AlekseyTs](https://github.com/AlekseyTs), [jjonescz](https://github.com/jjonescz) | | [MadsTorgersen](https://github.com/MadsTorgersen) |
| [Escape character](https://github.com/dotnet/csharplang/issues/7400) | N/A | [In Progress](https://github.com/dotnet/roslyn/pull/70497) | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) | [jcouv](https://github.com/jcouv), [RikkiGibson](https://github.com/RikkiGibson) | | [CyrusNajmabadi](https://github.com/CyrusNajmabadi) |
| [Method group natural type improvements](https://github.com/dotnet/csharplang/blob/main/proposals/method-group-natural-type-improvements.md) | main | In Progress | [jcouv](https://github.com/jcouv) | [AlekseyTs](https://github.com/AlekseyTs), [cston](https://github.com/cston) | | [jcouv](https://github.com/jcouv) |
| [Native lock](https://github.com/dotnet/csharplang/issues/7104) | main | In Progress | [jjonescz](https://github.com/jjonescz) | | | [stephentoub](https://github.com/stephentoub) |
| Implicit indexer access in object initializers | main | [Merged into 17.9p3](https://github.com/dotnet/roslyn/pull/70649) | [jcouv](https://github.com/jcouv) | [AlekseyTs](https://github.com/AlekseyTs), [cston](https://github.com/cston) | | |

# C# 12.0
Expand Down
7 changes: 7 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/Binder_Conversions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,13 @@ BoundExpression createConversion(
if (!hasErrors && conversion.Exists)
{
ensureAllUnderlyingConversionsChecked(syntax, source, conversion, wasCompilerGenerated, destination, diagnostics);

if (conversion.Kind == ConversionKind.ImplicitReference &&
source.Type is { } sourceType &&
sourceType.IsWellKnownTypeLock())
{
diagnostics.Add(ErrorCode.WRN_ConvertingLock, source.Syntax);
}
}

return new BoundConversion(
Expand Down
5 changes: 3 additions & 2 deletions src/Compilers/CSharp/Portable/Binder/Binder_Expressions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3182,13 +3182,14 @@ private BoundExpression BindOutVariableDeclarationArgument(
/// <summary>
/// Reports an error when a bad special by-ref local was found.
/// </summary>
internal static void CheckRestrictedTypeInAsyncMethod(Symbol containingSymbol, TypeSymbol type, BindingDiagnosticBag diagnostics, SyntaxNode syntax, bool forUsingExpression = false)
internal static void CheckRestrictedTypeInAsyncMethod(Symbol containingSymbol, TypeSymbol type, BindingDiagnosticBag diagnostics, SyntaxNode syntax, ErrorCode errorCode = ErrorCode.ERR_BadSpecialByRefLocal)
{
Debug.Assert(errorCode is ErrorCode.ERR_BadSpecialByRefLocal or ErrorCode.ERR_BadSpecialByRefUsing or ErrorCode.ERR_BadSpecialByRefLock);
if (containingSymbol.Kind == SymbolKind.Method
&& ((MethodSymbol)containingSymbol).IsAsync
&& type.IsRestrictedType())
{
Error(diagnostics, forUsingExpression ? ErrorCode.ERR_BadSpecialByRefUsing : ErrorCode.ERR_BadSpecialByRefLocal, syntax, type);
Error(diagnostics, errorCode, syntax, type);
}
}

Expand Down
10 changes: 10 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/LockBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ internal override BoundStatement BindLockStatementParts(BindingDiagnosticBag dia
Error(diagnostics, ErrorCode.ERR_LockNeedsReference, exprSyntax, exprType);
hasErrors = true;
}
else if (exprType.IsWellKnownTypeLock() &&
Compilation.GetWellKnownType(WellKnownType.System_Threading_Lock__Scope) is { } scopeType)
{
CheckRestrictedTypeInAsyncMethod(
originalBinder.ContainingMemberOrLambda,
scopeType,
diagnostics,
exprSyntax,
errorCode: ErrorCode.ERR_BadSpecialByRefLock);
}

BoundStatement stmt = originalBinder.BindPossibleEmbeddedStatement(_syntax.Statement, diagnostics);
Debug.Assert(this.Locals.IsDefaultOrEmpty);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ internal static BoundStatement BindUsingStatementOrDeclarationFromParts(SyntaxNo
Debug.Assert(expressionOpt is not null);
if (expressionOpt.Type is not null)
{
CheckRestrictedTypeInAsyncMethod(originalBinder.ContainingMemberOrLambda, expressionOpt.Type, diagnostics, expressionOpt.Syntax, forUsingExpression: true);
CheckRestrictedTypeInAsyncMethod(originalBinder.ContainingMemberOrLambda, expressionOpt.Type, diagnostics, expressionOpt.Syntax, errorCode: ErrorCode.ERR_BadSpecialByRefUsing);
}
}
else
Expand Down
9 changes: 9 additions & 0 deletions src/Compilers/CSharp/Portable/CSharpResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -7839,4 +7839,13 @@ To remove the warning, you can use /reference instead (set the Embed Interop Typ
<data name="IDS_ImplicitIndexerInitializer" xml:space="preserve">
<value>implicit indexer initializer</value>
</data>
<data name="WRN_ConvertingLock" xml:space="preserve">
<value>A value of type 'System.Threading.Lock' converted to another type will use likely unintended monitor-based locking in 'lock' statement.</value>
</data>
<data name="WRN_ConvertingLock_Title" xml:space="preserve">
<value>A value of type 'System.Threading.Lock' converted to another type will use likely unintended monitor-based locking in 'lock' statement.</value>
</data>
<data name="ERR_BadSpecialByRefLock" xml:space="preserve">
<value>A lock statement scope type '{0}' cannot be used in async methods or async lambda expressions.</value>
</data>
</root>
3 changes: 3 additions & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2285,6 +2285,9 @@ internal enum ErrorCode

#endregion

WRN_ConvertingLock = 9214,
ERR_BadSpecialByRefLock = 9215,

// Note: you will need to do the following after adding warnings:
// 1) Re-generate compiler code (eng\generate-compiler-code.cmd).
}
Expand Down
1 change: 1 addition & 0 deletions src/Compilers/CSharp/Portable/Errors/ErrorFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2411,6 +2411,7 @@ internal static bool IsBuildOnlyDiagnostic(ErrorCode code)
case ErrorCode.ERR_InvalidExperimentalDiagID:
case ErrorCode.ERR_SpreadMissingMember:
case ErrorCode.ERR_CollectionExpressionTargetNoElementType:
case ErrorCode.ERR_BadSpecialByRefLock:
return false;
default:
// NOTE: All error codes must be explicitly handled in this switch statement
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,11 @@ namespace Microsoft.CodeAnalysis.CSharp
internal sealed partial class LocalRewriter
{
/// <summary>
/// Lowers a lock statement to a try-finally block that calls Monitor.Enter and Monitor.Exit
/// before and after the body, respectively.
/// Lowers a lock statement to a try-finally block that calls (before and after the body, respectively):
/// <list type="bullet">
/// <item>Lock.EnterLockScope and Lock+Scope.Dispose if the argument is of type Lock, or</item>
/// <item>Monitor.Enter and Monitor.Exit.</item>
/// </list>
/// </summary>
public override BoundNode VisitLockStatement(BoundLockStatement node)
{
Expand All @@ -37,6 +40,41 @@ public override BoundNode VisitLockStatement(BoundLockStatement node)
argumentType); //need to have a non-null type here for TempHelpers.StoreToTemp.
}

if (argumentType.IsWellKnownTypeLock() &&
TryGetWellKnownTypeMember(lockSyntax, WellKnownMember.System_Threading_Lock__EnterLockScope, out MethodSymbol enterLockScope) &&
TryGetWellKnownTypeMember(lockSyntax, WellKnownMember.System_Threading_Lock__Scope__Dispose, out MethodSymbol lockScopeDispose))
{
// lock (x) { body } -> using (x.EnterLockScope()) { body }

var tryBlock = rewrittenBody is BoundBlock block ? block : BoundBlock.SynthesizedNoLocals(lockSyntax, rewrittenBody);

var enterLockScopeCall = BoundCall.Synthesized(
rewrittenArgument.Syntax,
rewrittenArgument,
initialBindingReceiverIsSubjectToCloning: ThreeState.Unknown,
enterLockScope);

BoundLocal boundTemp = _factory.StoreToTemp(enterLockScopeCall,
out BoundAssignmentOperator tempAssignment,
syntaxOpt: rewrittenArgument.Syntax,
kind: SynthesizedLocalKind.Using);
var expressionStatement = new BoundExpressionStatement(rewrittenArgument.Syntax, tempAssignment);

BoundStatement tryFinally = RewriteUsingStatementTryFinally(
rewrittenArgument.Syntax,
rewrittenArgument.Syntax,
tryBlock,
boundTemp,
awaitKeywordOpt: default,
awaitOpt: null,
patternDisposeInfo: MethodArgumentInfo.CreateParameterlessMethod(lockScopeDispose));

return new BoundBlock(
lockSyntax,
locals: [boundTemp.LocalSymbol],
statements: [expressionStatement, tryFinally]);
}

if (argumentType.Kind == SymbolKind.TypeParameter)
{
// If the argument has a type parameter type, then we'll box it right away
Expand Down
6 changes: 6 additions & 0 deletions src/Compilers/CSharp/Portable/Symbols/TypeSymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2078,6 +2078,12 @@ internal static bool IsWellKnownTypeIsExternalInit(this TypeSymbol typeSymbol)

internal static bool IsWellKnownTypeOutAttribute(this TypeSymbol typeSymbol) => typeSymbol.IsWellKnownInteropServicesTopLevelType("OutAttribute");

internal static bool IsWellKnownTypeLock(this TypeSymbol typeSymbol)
{
return typeSymbol.Name == "Lock" && typeSymbol.ContainingType is null &&
typeSymbol.IsContainedInNamespace("System", "Threading");
}

private static bool IsWellKnownInteropServicesTopLevelType(this TypeSymbol typeSymbol, string name)
{
if (typeSymbol.Name != name || typeSymbol.ContainingType is object)
Expand Down
15 changes: 15 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.cs.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.de.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.es.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions src/Compilers/CSharp/Portable/xlf/CSharpResources.fr.xlf

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 2cc133d

Please sign in to comment.