Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Caller of an async local function cannot expect any of the callee’s code to execute #64482

Merged
merged 6 commits into from
Oct 27, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
30 changes: 30 additions & 0 deletions docs/compilers/CSharp/Compiler Breaking Changes - DotNet 7.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
# This document lists known breaking changes in Roslyn after .NET 6 all the way to .NET 7.

## For the purpose of definite assignment analysis, invocations of async local functions are no longer treated as being awaited

***Introduced in Visual Studio 2022 version 17.5***

For the purpose of definite assignment analysis, invocations of an async local function is
no longer treated as being awaited and, therefore, the local function is not considered to
be fully executed. See https://github.com/dotnet/roslyn/issues/43697 for the rationale.

The code below is now going to report a definite assignment error:
```csharp
public async Task M()
{
bool a;
await M1();
Console.WriteLine(a); // error CS0165: Use of unassigned local variable 'a'

async Task M1()
{
if ("" == String.Empty)
{
throw new Exception();
}
else
{
a = true;
}
}
}
```

## Type tests for `ref` structs are not supported.

***Introduced in Visual Studio 2022 version 17.4***
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1320,7 +1320,11 @@ protected virtual void VisitLocalFunctionUse(
if (isCall)
{
Join(ref State, ref localFunctionState.StateFromBottom);
Meet(ref State, ref localFunctionState.StateFromTop);

if (!symbol.IsAsync)
{
Meet(ref State, ref localFunctionState.StateFromTop);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have a follow-up issue to track the decision to also perform this Meet when the call is "immediately awaited"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we have a follow-up issue to track the decision to also perform this Meet when the call is "immediately awaited"?

That is a language design change that I am not planning to champion at the moment.

}
}
localFunctionState.Visited = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1481,6 +1481,9 @@ async void L1()
}
}" + IAsyncDisposableDefinition);
comp.VerifyDiagnostics(
// (9,9): error CS0165: Use of unassigned local variable 'x'
// x++;
Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(9, 9),
// (10,9): error CS0165: Use of unassigned local variable 'y'
// y++;
Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(10, 9),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,9 @@ async void L1()
}
}");
comp.VerifyDiagnostics(
// (11,9): error CS0165: Use of unassigned local variable 'x'
// x++;
Diagnostic(ErrorCode.ERR_UseDefViolation, "x").WithArguments("x").WithLocation(11, 9),
// (12,9): error CS0165: Use of unassigned local variable 'y'
// y++;
Diagnostic(ErrorCode.ERR_UseDefViolation, "y").WithArguments("y").WithLocation(12, 9),
Expand Down
184 changes: 184 additions & 0 deletions src/Compilers/CSharp/Test/Semantic/Semantics/LocalFunctionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10072,5 +10072,189 @@ int P

Assert.Null(model.GetSymbolInfo(node).Symbol);
}

[Fact, WorkItem(43697, "https://github.com/dotnet/roslyn/issues/43697")]
public void DefiniteAssignment_01()
{
var text = @"
using System;
using System.Threading.Tasks;

#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.
#pragma warning disable CS1998 // This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

public class C
{
public void M()
{
bool a;
M1();
Console.WriteLine(a);
async Task M1()
{
if ("""" == String.Empty)
{
throw new Exception();
}
else
{
a = true;
}
}
}
}
";
CreateCompilation(text).VerifyDiagnostics(
// (14,27): error CS0165: Use of unassigned local variable 'a'
// Console.WriteLine(a);
Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(14, 27)
);
}

[Fact, WorkItem(43697, "https://github.com/dotnet/roslyn/issues/43697")]
public void DefiniteAssignment_02()
{
var text = @"
using System;
using System.Threading.Tasks;

public class C
{
public void M()
{
bool a;
M1();
Console.WriteLine(a);
Task M1()
{
if ("""" == String.Empty)
{
throw new Exception();
}
else
{
a = true;
}

return null;
}
}
}
";
CreateCompilation(text).VerifyDiagnostics();
}

[Fact, WorkItem(43697, "https://github.com/dotnet/roslyn/issues/43697")]
public void DefiniteAssignment_03()
{
var text = @"
using System;
using System.Threading.Tasks;

#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

public class C
{
public void M()
{
bool a;
M1();
Console.WriteLine(a);
async Task M1()
{
await Task.Yield();

if ("""" == String.Empty)
{
throw new Exception();
}
else
{
a = true;
}
}
}
}
";
CreateCompilation(text).VerifyDiagnostics(
// (13,27): error CS0165: Use of unassigned local variable 'a'
// Console.WriteLine(a);
Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(13, 27)
);
}

[Fact, WorkItem(43697, "https://github.com/dotnet/roslyn/issues/43697")]
public void DefiniteAssignment_04()
{
var text = @"
using System;
using System.Threading.Tasks;

#pragma warning disable CS1998 // This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

public class C
{
public async Task M()
{
bool a;
await M1();
Console.WriteLine(a);
async Task M1()
{
if ("""" == String.Empty)
{
throw new Exception();
}
else
{
a = true;
}
}
}
}
";
CreateCompilation(text).VerifyDiagnostics(
// (13,27): error CS0165: Use of unassigned local variable 'a'
// Console.WriteLine(a);
Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(13, 27)
);
}

[Fact, WorkItem(43697, "https://github.com/dotnet/roslyn/issues/43697")]
public void DefiniteAssignment_05()
{
var text = @"
using System;
using System.Threading.Tasks;

public class C
{
public async Task M()
{
bool a;
await M1();
Console.WriteLine(a);
async Task M1()
{
await Task.Yield();

if ("""" == String.Empty)
{
throw new Exception();
}
else
{
a = true;
}
}
}
}
";
CreateCompilation(text).VerifyDiagnostics(
// (11,27): error CS0165: Use of unassigned local variable 'a'
// Console.WriteLine(a);
Diagnostic(ErrorCode.ERR_UseDefViolation, "a").WithArguments("a").WithLocation(11, 27)
);
}
}
}