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

SemanticModel.GetDeconstructionInfo returns different results for not accessible call with/without await #72634

Open
syrompetka opened this issue Mar 21, 2024 · 1 comment
Labels
Area-Compilers Concept-Diagnostic Clarity The issues deals with the ease of understanding of errors and warnings.
Milestone

Comments

@syrompetka
Copy link

Version Used:
4.92

Steps to Reproduce:

using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Emit;

var codeWithInternals = @"
//[assembly: System.Runtime.CompilerServices.InternalsVisibleTo(""LibUsingInternals"")]
namespace LibWithInternals
{
    public class ClassWithInternals
    {
        internal async System.Threading.Tasks.Task< (int i1, int i2)> GetDataAsync()
        {
            await Task.Yield();
            return (10, 20);
        }

        internal (int i1, int i2) GetData()
        {
            return (10, 20);
        }
    }
}
";

var codeUsingInternals = @"
namespace LibUsingInternals
{
    internal class ClassUsingInternals
    {
        public async void DoWork()
        {
            var manager = new LibWithInternals.ClassWithInternals();

            var (i1, i2) = await manager.GetDataAsync();

            var (i3, i4) = manager.GetData();
        }
    }
}
";

var options = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary);
var internalsSyntaxTree = CSharpSyntaxTree.ParseText(codeWithInternals);
var libWithInternals = CSharpCompilation.Create("LibWithInternals",
    new[] { internalsSyntaxTree },
    new[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location), MetadataReference.CreateFromFile(typeof(Task).Assembly.Location) },
    options);

var stream = new MemoryStream();
var emitOptions = new EmitOptions(metadataOnly: true, tolerateErrors: true, includePrivateMembers: false);
var result = libWithInternals.Emit(stream, options: emitOptions);
var reference = MetadataReference.CreateFromStream(stream);

var syntaxTree = CSharpSyntaxTree.ParseText(codeUsingInternals);
var libUsingInternals = CSharpCompilation.Create("LibUsingInternals",
    new[] { syntaxTree },
    new MetadataReference[] { MetadataReference.CreateFromFile(typeof(object).Assembly.Location), libWithInternals.ToMetadataReference() },
    options);

var semanticModel = libUsingInternals.GetSemanticModel(syntaxTree);

var root = syntaxTree.GetRoot();
foreach (var assignment in root.DescendantNodes().OfType<AssignmentExpressionSyntax>())
{
    var deconstruction = semanticModel.GetDeconstructionInfo(assignment);

    Console.WriteLine("-------------------------------");
    Console.WriteLine(assignment.ToString());
    Console.WriteLine(deconstruction.Nested.Count());
}

Actual Behavior:

-------------------------------
var (i1, i2) = await manager.GetDataAsync()
0
-------------------------------
var (i3, i4) = manager.GetData()
2

Expected Behavior:
GetDeconstructionInfo should return same result for both assignments.

@dotnet-issue-labeler dotnet-issue-labeler bot added Area-Compilers untriaged Issues and PRs which have not yet been triaged by a lead labels Mar 21, 2024
@jaredpar jaredpar added this to the Backlog milestone Mar 22, 2024
@jcouv
Copy link
Member

jcouv commented Apr 3, 2024

There are indeed differences of behavior between the case with await and the one without. The diagnostics are also different (see below).

What's happening is the await doesn't get analyzed when the operand has any errors, even if it has a type. So the await doesn't get a type and the deconstruction doesn't types as a result either.
The same thing would happen with a faulty await in other contexts as well (in foreach, var local declaration, ...).
So this is an error recovery and diagnostic clarity question with await.

Here's the repro with diagnostics:

        [Fact]
        public void TODO2()
        {
            string lib_cs = """
public class C
{
    internal System.Threading.Tasks.Task<(int i1, int i2)> MAsync() => throw null;
    internal (int i1, int i2) M() => throw null;
}
""";
            string source = """
internal class D
{
    public async void DoWork()
    {
        var (i1, i2) = await new C().MAsync();
        var (i3, i4) = new C().M();
    }
}
""";
            var lib = CreateCompilation(lib_cs);
            lib.VerifyEmitDiagnostics();

            var comp = CreateCompilation(source, references: [lib.ToMetadataReference()]);
            comp.VerifyEmitDiagnostics(
                // (5,14): error CS8130: Cannot infer the type of implicitly-typed deconstruction variable 'i1'.
                //         var (i1, i2) = await new C().MAsync();
                Diagnostic(ErrorCode.ERR_TypeInferenceFailedForImplicitlyTypedDeconstructionVariable, "i1").WithArguments("i1").WithLocation(5, 14),
                // (5,18): error CS8130: Cannot infer the type of implicitly-typed deconstruction variable 'i2'.
                //         var (i1, i2) = await new C().MAsync();
                Diagnostic(ErrorCode.ERR_TypeInferenceFailedForImplicitlyTypedDeconstructionVariable, "i2").WithArguments("i2").WithLocation(5, 18),
                // (5,38): error CS0122: 'C.MAsync()' is inaccessible due to its protection level
                //         var (i1, i2) = await new C().MAsync();
                Diagnostic(ErrorCode.ERR_BadAccess, "MAsync").WithArguments("C.MAsync()").WithLocation(5, 38),
                // (6,32): error CS0122: 'C.M()' is inaccessible due to its protection level
                //         var (i3, i4) = new C().M();
                Diagnostic(ErrorCode.ERR_BadAccess, "M").WithArguments("C.M()").WithLocation(6, 32));

            var tree = comp.SyntaxTrees.First();
            var model = comp.GetSemanticModel(tree);

            var info1 = model.GetDeconstructionInfo(GetSyntax<AssignmentExpressionSyntax>(tree, "var (i1, i2) = await new C().MAsync()"));
            Assert.Null(info1.Method);
            Assert.Equal(0, info1.Nested.Length);

            var info2 = model.GetDeconstructionInfo(GetSyntax<AssignmentExpressionSyntax>(tree, "var (i3, i4) = new C().M()"));
            Assert.Null(info2.Method);
            Assert.Equal(2, info2.Nested.Length);
        }

@jcouv jcouv added the Concept-Diagnostic Clarity The issues deals with the ease of understanding of errors and warnings. label Apr 3, 2024
@jcouv jcouv removed the untriaged Issues and PRs which have not yet been triaged by a lead label Apr 15, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Compilers Concept-Diagnostic Clarity The issues deals with the ease of understanding of errors and warnings.
Projects
None yet
Development

No branches or pull requests

3 participants