Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ internal abstract class BoundTreeToDifferentEnclosingContextRewriter : BoundTree
// though its containing method is not correct because the code is moved into another method)
private readonly Dictionary<LocalSymbol, LocalSymbol> localMap = new Dictionary<LocalSymbol, LocalSymbol>();

//to handle type changes (e.g. type parameters) we need to update placeholders
private readonly Dictionary<BoundValuePlaceholderBase, BoundExpression> _placeholderMap = new Dictionary<BoundValuePlaceholderBase, BoundExpression>();

// A mapping for types in the original method to types in its replacement. This is mainly necessary
// when the original method was generic, as type parameters in the original method are mapping into
// type parameters of the resulting class.
Expand Down Expand Up @@ -114,6 +117,31 @@ protected BoundBlock VisitBlock(BoundBlock node, bool removeInstrumentation)
return TypeMap.SubstituteType(type).Type;
}

public override BoundNode VisitAwaitableInfo(BoundAwaitableInfo node)
{
var awaitablePlaceholder = node.AwaitableInstancePlaceholder;
if (awaitablePlaceholder is null)
{
return node;
}

var rewrittenPlaceholder = awaitablePlaceholder.Update(VisitType(awaitablePlaceholder.Type));
_placeholderMap.Add(awaitablePlaceholder, rewrittenPlaceholder);

var getAwaiter = (BoundExpression?)this.Visit(node.GetAwaiter);
var isCompleted = VisitPropertySymbol(node.IsCompleted);
var getResult = VisitMethodSymbol(node.GetResult);

_placeholderMap.Remove(awaitablePlaceholder);

return node.Update(rewrittenPlaceholder, node.IsDynamic, getAwaiter, isCompleted, getResult);
}

public override BoundNode VisitAwaitableValuePlaceholder(BoundAwaitableValuePlaceholder node)
{
return _placeholderMap[node];
}

protected override BoundBinaryOperator.UncommonData? VisitBinaryOperatorData(BoundBinaryOperator node)
{
// Local rewriter should have already rewritten interpolated strings into their final form of calls and gotos
Expand Down Expand Up @@ -142,6 +170,24 @@ protected BoundBlock VisitBlock(BoundBlock node, bool removeInstrumentation)
VisitType(node.Type));
}

[return: NotNullIfNotNull(nameof(property))]
public override PropertySymbol? VisitPropertySymbol(PropertySymbol? property)
{
if (property is null)
{
return null;
}
if (property.ContainingType.IsAnonymousType)
{
//at this point we expect that the code is lowered and that getters of anonymous types are accessed
//only via their corresponding get-methods (see VisitMethodSymbol)
throw ExceptionUtilities.Unreachable();
}
return ((PropertySymbol)property.OriginalDefinition)
.AsMember((NamedTypeSymbol)TypeMap.SubstituteType(property.ContainingType).AsTypeSymbolOnly())
;
}

[return: NotNullIfNotNull(nameof(method))]
public override MethodSymbol? VisitMethodSymbol(MethodSymbol? method)
{
Expand Down
28 changes: 0 additions & 28 deletions src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,6 @@ internal abstract partial class MethodToClassRewriter : BoundTreeToDifferentEncl
protected readonly BindingDiagnosticBag Diagnostics;
protected readonly VariableSlotAllocator? slotAllocator;

private readonly Dictionary<BoundValuePlaceholderBase, BoundExpression> _placeholderMap;

protected MethodToClassRewriter(VariableSlotAllocator? slotAllocator, TypeCompilationState compilationState, BindingDiagnosticBag diagnostics)
{
Debug.Assert(compilationState != null);
Expand All @@ -46,7 +44,6 @@ protected MethodToClassRewriter(VariableSlotAllocator? slotAllocator, TypeCompil
this.CompilationState = compilationState;
this.Diagnostics = diagnostics;
this.slotAllocator = slotAllocator;
this._placeholderMap = new Dictionary<BoundValuePlaceholderBase, BoundExpression>();
}

/// <summary>
Expand Down Expand Up @@ -254,31 +251,6 @@ private bool TryGetHoistedField(Symbol variable, [NotNullWhen(true)] out FieldSy
return false;
}

public override BoundNode VisitAwaitableInfo(BoundAwaitableInfo node)
{
var awaitablePlaceholder = node.AwaitableInstancePlaceholder;
if (awaitablePlaceholder is null)
{
return node;
}

var rewrittenPlaceholder = awaitablePlaceholder.Update(VisitType(awaitablePlaceholder.Type));
_placeholderMap.Add(awaitablePlaceholder, rewrittenPlaceholder);

var getAwaiter = (BoundExpression?)this.Visit(node.GetAwaiter);
var isCompleted = VisitPropertySymbol(node.IsCompleted);
var getResult = VisitMethodSymbol(node.GetResult);

_placeholderMap.Remove(awaitablePlaceholder);

return node.Update(rewrittenPlaceholder, node.IsDynamic, getAwaiter, isCompleted, getResult);
}

public override BoundNode VisitAwaitableValuePlaceholder(BoundAwaitableValuePlaceholder node)
{
return _placeholderMap[node];
}

public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node)
{
BoundExpression originalLeft = node.Left;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -505,11 +505,18 @@ private void FreeReusableHoistedField(StateMachineFieldSymbol field)
fields.Add(field);
}

private BoundExpression HoistRefInitialization(SynthesizedLocal local, BoundAssignmentOperator node)
private BoundExpression HoistRefInitialization(LocalSymbol local, BoundAssignmentOperator node)
{
Debug.Assert(
local switch
{
TypeSubstitutedLocalSymbol tsl => tsl.UnderlyingLocalSymbol,
_ => local
} is SynthesizedLocal
);
Debug.Assert(local.SynthesizedKind == SynthesizedLocalKind.Spill ||
(local.SynthesizedKind == SynthesizedLocalKind.ForEachArray && local.Type.HasInlineArrayAttribute(out _) && local.Type.TryGetInlineArrayElementField() is object));
Debug.Assert(local.SyntaxOpt != null);
Debug.Assert(local.GetDeclaratorSyntax() != null);
#pragma warning disable format
Debug.Assert(local.SynthesizedKind switch
{
Expand Down Expand Up @@ -856,7 +863,7 @@ public override BoundNode VisitAssignmentOperator(BoundAssignmentOperator node)

// We have an assignment to a variable that has not yet been assigned a proxy.
// So we assign the proxy before translating the assignment.
return HoistRefInitialization((SynthesizedLocal)leftLocal, node);
return HoistRefInitialization(leftLocal, node);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,15 @@ public TypeSubstitutedLocalSymbol(LocalSymbol originalVariable, TypeWithAnnotati
Debug.Assert(containingSymbol != null);
Debug.Assert(containingSymbol.DeclaringCompilation is not null);

_originalVariable = originalVariable;
//avoid double wrapping
//this can happen e.g. if a local is substituted in ExtensionMethodBodyRewriter
//and later it is substituted for example in ClosureConversion again
_originalVariable = originalVariable switch
{
TypeSubstitutedLocalSymbol tsl => tsl._originalVariable,
var l => l
};

_type = type;
_containingSymbol = containingSymbol;
}
Expand Down Expand Up @@ -123,6 +131,8 @@ internal override ReadOnlyBindingDiagnostic<AssemblySymbol> GetConstantValueDiag
return _originalVariable.GetConstantValueDiagnostics(boundInitValue);
}

internal LocalSymbol UnderlyingLocalSymbol => _originalVariable;

internal override LocalSymbol WithSynthesizedLocalKindAndSyntax(
SynthesizedLocalKind kind, SyntaxNode syntax
#if DEBUG
Expand Down
116 changes: 116 additions & 0 deletions src/Compilers/CSharp/Test/Semantic/Semantics/AwaitExpressionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,122 @@ async void Goo(Task<int> t)
Assert.Equal("System.Boolean System.Runtime.CompilerServices.TaskAwaiter<System.Int32>.IsCompleted { get; }", info.IsCompletedProperty.ToTestDisplayString());
}

[Fact]
[WorkItem("https://github.com/dotnet/roslyn/issues/76999")]
public void TestAwaitHoistedRef()
{
var src = """
using System.Threading.Tasks;

public sealed class RefHolder<T>
{
private T _t;
public ref T Get() => ref _t;
}

public static class App
{
public static void Do<T>()
{
var res = new RefHolder<T>();
M().Wait();
async Task M()
{
res.Get() = await Task.FromResult(default(T));
}
}
}
""";

var comp = CreateCompilation(src);
comp.VerifyEmitDiagnostics(
// (17,13): error CS8178: A reference returned by a call to 'RefHolder<T>.Get()' cannot be preserved across 'await' or 'yield' boundary.
// res.Get() = await Task.FromResult(default(T));
Diagnostic(ErrorCode.ERR_RefReturningCallAndAwait, "res.Get()").WithArguments("RefHolder<T>.Get()").WithLocation(17, 13)
);
}

[Fact]
[WorkItem("https://github.com/dotnet/roslyn/issues/76999")]
public void TestAwaitHoistedRef_InNewExtensionContainer()
{
var src = """
using System.Threading.Tasks;

public sealed class RefHolder<T>
{
private T _t;
public ref T Get() => ref _t;
}

public static class App
{
extension(int)
{
public static void Do<T>()
{
var res = new RefHolder<T>();
M().Wait();
async Task M()
{
res.Get() = await Task.FromResult(default(T));
}
}
}
}
""";

var comp = CreateCompilation(src);
comp.VerifyEmitDiagnostics(
// (19,17): error CS8178: A reference returned by a call to 'RefHolder<T>.Get()' cannot be preserved across 'await' or 'yield' boundary.
// res.Get() = await Task.FromResult(default(T));
Diagnostic(ErrorCode.ERR_RefReturningCallAndAwait, "res.Get()").WithArguments("RefHolder<T>.Get()").WithLocation(19, 17)
);
}

[Fact]
[WorkItem("https://github.com/dotnet/roslyn/issues/76999")]
public void TestAwaitHoistedRef2()
{
var src = """
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

public sealed class ValuesHolder<T>
{
private readonly T[] _values = new T[10];

public ref T this[int type] => ref _values[type];
}

public static class App
{
public static async Task<ValuesHolder<TResult>> Do<TResult>()
{
var res = new ValuesHolder<TResult>();

var taskGroup = new List<KeyValuePair<int, Task<TResult>>>();

await Task.WhenAll(taskGroup.Select(async kv =>
{
res[0] = await kv.Value;
}));

return res;
}
}
""";

var comp = CreateCompilation(src);
comp.VerifyEmitDiagnostics(
// (23,4): error CS8178: A reference returned by a call to 'ValuesHolder<TResult>.this[int].get' cannot be preserved across 'await' or 'yield' boundary.
// res[0] = await kv.Value;
Diagnostic(ErrorCode.ERR_RefReturningCallAndAwait, "res[0]").WithArguments("ValuesHolder<TResult>.this[int].get").WithLocation(23, 4)
);
}

[Fact]
[WorkItem(1084696, "http://vstfdevdiv:8080/DevDiv2/DevDiv/_workitems/edit/1084696")]
public void TestAwaitInfo2()
Expand Down