dotnet / roslyn

The Roslyn .NET compiler provides C# and Visual Basic languages with rich code analysis APIs.
https://docs.microsoft.com/dotnet/csharp/roslyn-sdk/
MIT License
19.04k stars 4.03k forks source link

Completion Service does not provide all methods available. #73427

Closed b12-144 closed 6 months ago

b12-144 commented 6 months ago

When using CompletionService to get all available properties and methods for a DbSet, it does not return all methods. In the example below, it does not return Where, Select, SelectMany, Join. All methods available under System.Linq.Queryable.

Version Used: 8.0.204

Steps to Reproduce: Project .csproj file:

<Project Sdk="Microsoft.NET.Sdk">

    <PropertyGroup>
        <OutputType>Exe</OutputType>
        <TargetFramework>net8.0</TargetFramework>
        <ImplicitUsings>enable</ImplicitUsings>
        <Nullable>enable</Nullable>
    </PropertyGroup>

    <ItemGroup>
        <PackageReference Include="Microsoft.CodeAnalysis.Common" Version="4.9.0-3.final"/>
        <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.0-3.final"/>
        <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.9.0-3.final"/>
        <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Scripting" Version="4.9.0-3.final"/>
        <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.9.0-3.final"/>
        <PackageReference Include="System.ComponentModel.Composition" Version="8.0.0"/>
        <PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="4.9.0-3.final" />
        <PackageReference Include="System.Composition.TypedParts" Version="8.0.0" />
        <PackageReference Include="MySql.EntityFrameworkCore" Version="7.0.2" />
    </ItemGroup>
</Project>

Code:

public class MyProgram {
    public MyProgram() {
    }

    public async Task TestCompleter() {
        var host = MefHostServices.Create(MefHostServices.DefaultAssemblies);
        var workspace = new AdhocWorkspace(host);

        var code = @"using System;
            using Microsoft.EntityFrameworkCore;

            public class Product {
                public long ID { get; set; }
                public string Name { get; set; }
            }

            public class MyDbContext: DbContext {
                public DbSet<Product> Products { get; set; }
            }

            public class MyClass
            {
                public static void MyMethod(int value)
                {
                    var db = new MyDbContext();
                    db.Products.
                }
            }";

        var projectInfo = ProjectInfo
            .Create(ProjectId.CreateNewId(), VersionStamp.Create(), "MyProject", "MyProject", LanguageNames.CSharp)
            .WithMetadataReferences(new[] { 
                MetadataReference.CreateFromFile(typeof(object).Assembly.Location),
                MetadataReference.CreateFromFile(Assembly.Load("Microsoft.EntityFrameworkCore").Location),
                MetadataReference.CreateFromFile(Assembly.Load("System.Linq.Queryable").Location),
                MetadataReference.CreateFromFile(Assembly.Load("System.Linq.Expressions").Location)
            });
        var project = workspace.AddProject(projectInfo);
        var document = workspace.AddDocument(project.Id, "MyFile.cs", SourceText.From(code));

        var completionService = CompletionService.GetService(document);
        var results = await completionService.GetCompletionsAsync(document, code.LastIndexOf("db.Products.") + 12);
        var itemsList = results.ItemsList;
        //Method Where is not listed 
    }
}

Expected Behavior: Return list should contain methods: Where, Select, SelectMany, Join.

Actual Behavior: Only methods from DbSet class are returned. image

DoctorKrolic commented 6 months ago

You don't have using System.Linq; in your source, thus so far behavior is expected. Does it change if you add the suggested using to the code?

b12-144 commented 6 months ago

No, it does not work even after adding using System.Linq; in the code or adding MetadataReference.CreateFromFile(Assembly.Load("System.Linq").Location). Methods like Where, Select are not returned.

CyrusNajmabadi commented 6 months ago

Does your workspace or compilation contain any diagnostics?

DoctorKrolic commented 6 months ago

You have some problems with reference assemblies. When I added framework references using Basic.Reference.Assemblies package I got your code working: Project file:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <ImplicitUsings>enable</ImplicitUsings>
    <Nullable>enable</Nullable>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2"/>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Features" Version="4.9.2"/>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.9.2"/>
    <PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.4" />
    <PackageReference Include="Basic.Reference.Assemblies" Version="1.7.2" />
    </ItemGroup>
</Project>

Code:

using Basic.Reference.Assemblies;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Text;
using System;
using System.Reflection;

var host = MefHostServices.Create(MefHostServices.DefaultAssemblies);
var workspace = new AdhocWorkspace(host);

var code = """
    using System;
    using System.Linq;
    using Microsoft.EntityFrameworkCore;

    public class Product
    {
        public long ID { get; set; }
        public string Name { get; set; }
    }

    public class MyDbContext: DbContext
    {
        public DbSet<Product> Products { get; set; }
    }

    public class MyClass
    {
        public static void MyMethod(int value)
        {
            var db = new MyDbContext();
            db.Products.
        }
    }
    """;

var projectInfo = ProjectInfo
    .Create(ProjectId.CreateNewId(), VersionStamp.Create(), "MyProject", "MyProject", LanguageNames.CSharp)
    .WithMetadataReferences(
    [
        .. Net80.References.All,
        MetadataReference.CreateFromFile(Assembly.Load("Microsoft.EntityFrameworkCore").Location),
        MetadataReference.CreateFromFile(Assembly.Load("System.Linq.Queryable").Location),
        MetadataReference.CreateFromFile(Assembly.Load("System.Linq.Expressions").Location)
    ]);
var project = workspace.AddProject(projectInfo);
var document = workspace.AddDocument(project.Id, "MyFile.cs", SourceText.From(code));

var completionService = CompletionService.GetService(document);
var results = await completionService.GetCompletionsAsync(document, code.LastIndexOf("db.Products.") + 12);
var itemsList = results.ItemsList;
Console.WriteLine(itemsList.Count);

With this setup 120+ completions are returned, including all linq ones you were interested in

b12-144 commented 6 months ago

Thank you so much @DoctorKrolic , it worked!