File: SemanticModelReuse\SemanticModelReuseTests.cs
Web Access
Project: src\src\Workspaces\CoreTest\Microsoft.CodeAnalysis.Workspaces.UnitTests.csproj (Microsoft.CodeAnalysis.Workspaces.UnitTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
#nullable disable
 
using System.Threading;
using System.Threading.Tasks;
using Basic.Reference.Assemblies;
using Microsoft.CodeAnalysis.Collections;
using Microsoft.CodeAnalysis.CSharp.Extensions.ContextQuery;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Test.Utilities;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.VisualBasic.Extensions.ContextQuery;
using Roslyn.Test.Utilities;
using Xunit;
 
namespace Microsoft.CodeAnalysis.UnitTests.SemanticModelReuse;
 
[UseExportProvider]
public sealed class SemanticModelReuseTests
{
    private static Document CreateDocument(string code, string language)
    {
        var solution = new AdhocWorkspace().CurrentSolution;
        var projectId = ProjectId.CreateNewId();
        var project = solution.AddProject(projectId, "Project", "Project.dll", language).GetProject(projectId);
 
        return project.AddMetadataReference(Net40.References.mscorlib)
                      .AddDocument("Document", SourceText.From(code));
    }
 
    #region C# tests
 
    [Fact]
    public async Task NullBodyReturnsNormalSemanticModel1_CSharp()
    {
        var document = CreateDocument("", LanguageNames.CSharp);
 
        // trying to get a model for null should return a non-speculative model
        var model = await document.ReuseExistingSpeculativeModelAsync(null, CancellationToken.None);
        Assert.False(model.IsSpeculativeSemanticModel);
    }
 
    [Fact]
    public async Task NullBodyReturnsNormalSemanticModel2_CSharp()
    {
        var source = "class C { void M() { return; } }";
        var document = CreateDocument(source, LanguageNames.CSharp);
 
        // Even if we've primed things with a real location, getting a semantic model for null should return a
        // non-speculative model.
        var model1 = await document.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        var model2 = await document.ReuseExistingSpeculativeModelAsync(null, CancellationToken.None);
        Assert.False(model1.IsSpeculativeSemanticModel);
        Assert.False(model2.IsSpeculativeSemanticModel);
    }
 
    [Fact]
    public async Task SameSyntaxTreeReturnsNonSpeculativeModel_CSharp()
    {
        var source = "class C { void M() { return; } }";
        var document = CreateDocument(source, LanguageNames.CSharp);
 
        // First call will prime the cache to point at the real semantic model.  The next call will also use the
        // same syntax tree, so it should get the same semantic model.
        var model1 = await document.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        var model2 = await document.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.False(model1.IsSpeculativeSemanticModel);
        Assert.False(model2.IsSpeculativeSemanticModel);
 
        // Should be the same models.
        Assert.Equal(model1, model2);
 
        // Which also should be the normal model the document provides.
        var actualModel = await document.GetSemanticModelAsync();
        Assert.Equal(model1, actualModel);
    }
 
    [Fact]
    public async Task InBodyEditShouldProduceCachedModel_CSharp()
    {
        var source = "class C { void M() { return; } }";
        var document1 = CreateDocument(source, LanguageNames.CSharp);
 
        // First call will prime the cache to point at the real semantic model.
        var model1 = await document1.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.False(model1.IsSpeculativeSemanticModel);
 
        var document2 = document1.WithText(SourceText.From("class C { void M() { return null; } }"));
 
        // This should be able to get a speculative model using the original model we primed the cache with.
        var model2 = await document2.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.True(model2.IsSpeculativeSemanticModel);
    }
 
    [Fact]
    public async Task OutOfBodyEditShouldProduceFreshModel_CSharp()
    {
        var source = "class C { void M() { return; } }";
        var document1 = CreateDocument(source, LanguageNames.CSharp);
 
        // First call will prime the cache to point at the real semantic model.
        var model1 = await document1.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.False(model1.IsSpeculativeSemanticModel);
 
        var document2 = document1.WithText(SourceText.From("class C { long M() { return; } }"));
 
        // We changed the return type, so we can't reuse the previous model.
        var model2 = await document2.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.False(model2.IsSpeculativeSemanticModel);
 
        var document3 = document2.WithText(SourceText.From("class C { long M() { return 0; } }"));
 
        // We are now again only editing a method body so we should be able to get a speculative model.
        var model3 = await document3.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.True(model3.IsSpeculativeSemanticModel);
    }
 
    [Fact]
    public async Task MultipleBodyEditsShouldProduceFreshModel_CSharp()
    {
        var source = "class C { void M() { return; } }";
        var document1 = CreateDocument(source, LanguageNames.CSharp);
 
        // First call will prime the cache to point at the real semantic model.
        var model1 = await document1.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.False(model1.IsSpeculativeSemanticModel);
 
        var document2 = document1.WithText(SourceText.From("class C { void M() { return 0; } }"));
 
        // This should be able to get a speculative model using the original model we primed the cache with.
        var model2 = await document2.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.True(model2.IsSpeculativeSemanticModel);
 
        var document3 = document1.WithText(SourceText.From("class C { void M() { return 1; } }"));
 
        // This should be able to get a speculative model using the original model we primed the cache with.
        var model3 = await document3.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.True(model3.IsSpeculativeSemanticModel);
    }
 
    [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1167540")]
    public async Task MultipleBodyEditsShouldProduceFreshModel_Accessor_Property_CSharp()
    {
        var source = "class C { int M { get { return 0; } } }";
        var document1 = CreateDocument(source, LanguageNames.CSharp);
 
        // First call will prime the cache to point at the real semantic model.
        var model1 = await document1.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.False(model1.IsSpeculativeSemanticModel);
 
        var document2 = document1.WithText(SourceText.From("class C { int M { get { return 1; } } }"));
 
        // This should be able to get a speculative model using the original model we primed the cache with.
        var model2 = await document2.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.True(model2.IsSpeculativeSemanticModel);
 
        var document3 = document1.WithText(SourceText.From("class C { int M { get { return 2; } } }"));
 
        // This should be able to get a speculative model using the original model we primed the cache with.
        var model3 = await document3.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.True(model3.IsSpeculativeSemanticModel);
    }
 
    [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1167540")]
    public async Task MultipleBodyEditsShouldProduceFreshModel_Accessor_Event_CSharp()
    {
        var source = "class C { event System.Action E { add { return 0; } } }";
        var document1 = CreateDocument(source, LanguageNames.CSharp);
 
        // First call will prime the cache to point at the real semantic model.
        var model1 = await document1.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.False(model1.IsSpeculativeSemanticModel);
 
        var document2 = document1.WithText(SourceText.From("class C { event System.Action E { add { return 1; } } }"));
 
        // This should be able to get a speculative model using the original model we primed the cache with.
        var model2 = await document2.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.True(model2.IsSpeculativeSemanticModel);
 
        var document3 = document1.WithText(SourceText.From("class C { event System.Action E { add { return 2; } } }"));
 
        // This should be able to get a speculative model using the original model we primed the cache with.
        var model3 = await document3.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.True(model3.IsSpeculativeSemanticModel);
    }
 
    [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1167540")]
    public async Task MultipleBodyEditsShouldProduceFreshModel_Accessor_Indexer_CSharp()
    {
        var source = "class C { int this[int i] { get { return 0; } } }";
        var document1 = CreateDocument(source, LanguageNames.CSharp);
 
        // First call will prime the cache to point at the real semantic model.
        var model1 = await document1.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.False(model1.IsSpeculativeSemanticModel);
 
        var document2 = document1.WithText(SourceText.From("class C { int this[int i] { get { return 1; } } }"));
 
        // This should be able to get a speculative model using the original model we primed the cache with.
        var model2 = await document2.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.True(model2.IsSpeculativeSemanticModel);
 
        var document3 = document1.WithText(SourceText.From("class C { int this[int i] { get { return 2; } } }"));
 
        // This should be able to get a speculative model using the original model we primed the cache with.
        var model3 = await document3.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.True(model3.IsSpeculativeSemanticModel);
    }
 
    [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1541001")]
    [WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1587699")]
    public async Task TestOutOfBoundsInSyntaxContext1_CSharp()
    {
        var source = "class C { void M() { return; } }";
        var document1 = CreateDocument(source, LanguageNames.CSharp);
 
        // First call will prime the cache to point at the real semantic model.
        var model1 = await document1.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.False(model1.IsSpeculativeSemanticModel);
 
        var document2 = document1.WithText(SourceText.From("class C { void M() { return null; } }"));
 
        // This should be able to get a speculative model using the original model we primed the cache with.
        var model2 = await document2.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.True(model2.IsSpeculativeSemanticModel);
 
        // ensure this doesn't crash.
        CSharpSyntaxContext.CreateContext(document2, model2, source.IndexOf("void"), CancellationToken.None);
    }
 
    [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1541001")]
    [WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1587699")]
    public async Task TestOutOfBoundsInSyntaxContext2_CSharp()
    {
        // These two tree are considered equavilent at top level, but the change in trivia around the method
        // makes it tricky to decide whether it's safe to use the speculative model at a given position.
 
        var source1 = "class C { void M() { return; } }";
        //                                ^ this is the position used to set OriginalPositionForSpeculation when creating the speculative model.  
        var source2 = "class C {                             void M() { return null; } }";
        //                                                            ^ it's unsafe to use the speculative model at this position, even though it's after OriginalPositionForSpeculation 
 
        // First call will prime the cache to point at the real semantic model.
        var document1 = CreateDocument(source1, LanguageNames.CSharp);
        var model1 = await document1.ReuseExistingSpeculativeModelAsync(source1.IndexOf("return"), CancellationToken.None);
        Assert.False(model1.IsSpeculativeSemanticModel);
 
        var document2 = document1.WithText(SourceText.From(source2));
 
        // Because the change in trivia shifted the method definition, we are not able to get a speculative model based on previous model
        var model2 = await document2.ReuseExistingSpeculativeModelAsync(source2.IndexOf("{ return"), CancellationToken.None);
        Assert.False(model2.IsSpeculativeSemanticModel);
 
        // ensure this doesn't crash.
        CSharpSyntaxContext.CreateContext(document2, model2, source2.IndexOf("{ return"), CancellationToken.None);
    }
 
    #endregion
 
    #region Visual Basic tests
 
    [Fact]
    public async Task NullBodyReturnsNormalSemanticModel1_VisualBasic()
    {
        var document = CreateDocument("", LanguageNames.VisualBasic);
 
        // trying to get a model for null should return a non-speculative model
        var model = await document.ReuseExistingSpeculativeModelAsync(null, CancellationToken.None);
        Assert.False(model.IsSpeculativeSemanticModel);
    }
 
    [Fact]
    public async Task NullBodyReturnsNormalSemanticModel2_VisualBasic()
    {
        var source = """
            class C
                sub M()
                    return
                end sub
            end class
            """;
        var document = CreateDocument(source, LanguageNames.VisualBasic);
 
        // Even if we've primed things with a real location, getting a semantic model for null should return a
        // non-speculative model.
        var model1 = await document.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        var model2 = await document.ReuseExistingSpeculativeModelAsync(null, CancellationToken.None);
        Assert.False(model1.IsSpeculativeSemanticModel);
        Assert.False(model2.IsSpeculativeSemanticModel);
    }
 
    [Fact]
    public async Task SameSyntaxTreeReturnsNonSpeculativeModel_VisualBasic()
    {
        var source = """
            class C
                sub M()
                    return
                end sub
            end class
            """;
        var document = CreateDocument(source, LanguageNames.VisualBasic);
 
        // First call will prime the cache to point at the real semantic model.  The next call will also use the
        // same syntax tree, so it should get the same semantic model.
        var model1 = await document.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        var model2 = await document.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.False(model1.IsSpeculativeSemanticModel);
        Assert.False(model2.IsSpeculativeSemanticModel);
 
        // Should be the same models.
        Assert.Equal(model1, model2);
 
        // Which also should be the normal model the document provides.
        var actualModel = await document.GetSemanticModelAsync();
        Assert.Equal(model1, actualModel);
    }
 
    [Fact]
    public async Task InBodyEditShouldProduceCachedModel_VisualBasic()
    {
        var source = """
            class C
                sub M()
                    return
                end sub
            end class
            """;
        var document1 = CreateDocument(source, LanguageNames.VisualBasic);
 
        // First call will prime the cache to point at the real semantic model.
        var model1 = await document1.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.False(model1.IsSpeculativeSemanticModel);
 
        var document2 = document1.WithText(SourceText.From("""
            class C
                sub M()
                    return nothing
                end sub
            end class
            """));
 
        // This should be able to get a speculative model using the original model we primed the cache with.
        var model2 = await document2.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.True(model2.IsSpeculativeSemanticModel);
    }
 
    [Fact]
    public async Task OutOfBodyEditShouldProduceFreshModel_VisualBasic()
    {
        var source1 = """
            class C
                sub M()
                    return
                end sub
            end class
            """;
        var document1 = CreateDocument(source1, LanguageNames.VisualBasic);
 
        // First call will prime the cache to point at the real semantic model.
        var model1 = await document1.ReuseExistingSpeculativeModelAsync(source1.IndexOf("return"), CancellationToken.None);
        Assert.False(model1.IsSpeculativeSemanticModel);
 
        var source2 = """
            class C
                function M() as long
                    return
                end function
            end class
            """;
        var document2 = document1.WithText(SourceText.From(source2));
 
        // We changed the return type, so we can't reuse the previous model.
        var model2 = await document2.ReuseExistingSpeculativeModelAsync(source2.IndexOf("return"), CancellationToken.None);
        Assert.False(model2.IsSpeculativeSemanticModel);
 
        var document3 = document2.WithText(SourceText.From("""
            class C
                function M() as long
                    return 0
                end function
            end class
            """));
 
        // We are now again only editing a method body so we should be able to get a speculative model.
        var model3 = await document3.ReuseExistingSpeculativeModelAsync(source2.IndexOf("return"), CancellationToken.None);
        Assert.True(model3.IsSpeculativeSemanticModel);
    }
 
    [Fact]
    public async Task MultipleBodyEditsShouldProduceFreshModel_VisualBasic()
    {
        var source = """
            class C
                sub M()
                    return
                end sub
            end class
            """;
        var document1 = CreateDocument(source, LanguageNames.VisualBasic);
 
        // First call will prime the cache to point at the real semantic model.
        var model1 = await document1.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.False(model1.IsSpeculativeSemanticModel);
 
        var document2 = document1.WithText(SourceText.From("""
            class C
                sub M()
                    return 0
                end sub
            end class
            """));
 
        // This should be able to get a speculative model using the original model we primed the cache with.
        var model2 = await document2.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.True(model2.IsSpeculativeSemanticModel);
 
        var document3 = document1.WithText(SourceText.From("""
            class C
                sub M()
                    return 1
                end sub
            end class
            """));
 
        // This should be able to get a speculative model using the original model we primed the cache with.
        var model3 = await document3.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.True(model3.IsSpeculativeSemanticModel);
    }
 
    [Fact]
    public async Task MultipleBodyEditsShouldProduceFreshModel_Accessor_Property_VisualBasic()
    {
        var source = """
            class C
                readonly property M as integer
                    get
                        return 0
                    end get
                end property
            end class
            """;
        var document1 = CreateDocument(source, LanguageNames.VisualBasic);
 
        // First call will prime the cache to point at the real semantic model.
        var model1 = await document1.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.False(model1.IsSpeculativeSemanticModel);
 
        var document2 = document1.WithText(SourceText.From("""
            class C
                readonly property M as integer
                    get
                        return 1
                    end get
                end property
            end class
            """));
 
        // This should be able to get a speculative model using the original model we primed the cache with.
        var model2 = await document2.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.True(model2.IsSpeculativeSemanticModel);
 
        var document3 = document1.WithText(SourceText.From("""
            class C
                readonly property M as integer
                    get
                        return 2
                    end get
                end property
            end class
            """));
 
        // This should be able to get a speculative model using the original model we primed the cache with.
        var model3 = await document3.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.True(model3.IsSpeculativeSemanticModel);
    }
 
    [Fact]
    public async Task MultipleBodyEditsShouldProduceFreshModel_Accessor_Event_VisualBasic()
    {
        var source = """
            class C
                public custom event E as System.Action
                    addhandler(value as System.Action)
                        return 0
                    end addhandler
                end event
            end class
            """;
        var document1 = CreateDocument(source, LanguageNames.VisualBasic);
 
        // First call will prime the cache to point at the real semantic model.
        var model1 = await document1.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.False(model1.IsSpeculativeSemanticModel);
 
        var document2 = document1.WithText(SourceText.From("""
            class C
                public custom event E as System.Action
                    addhandler(value as System.Action)
                        return 1
                    end addhandler
                end event
            end class
            """));
 
        // This should be able to get a speculative model using the original model we primed the cache with.
        var model2 = await document2.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.True(model2.IsSpeculativeSemanticModel);
 
        var document3 = document1.WithText(SourceText.From("""
            class C
                public custom event E as System.Action
                    addhandler(value as System.Action)
                        return 2
                    end addhandler
                end event
            end class
            """));
 
        // This should be able to get a speculative model using the original model we primed the cache with.
        var model3 = await document3.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.True(model3.IsSpeculativeSemanticModel);
    }
 
    [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1541001")]
    [WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1587699")]
    public async Task TestOutOfBoundsInSyntaxContext1_VisualBasic()
    {
        var source = """
            class C
                sub M()
                    return
                end sub
            end class
            """;
        var document1 = CreateDocument(source, LanguageNames.VisualBasic);
 
        // First call will prime the cache to point at the real semantic model.
        var model1 = await document1.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.False(model1.IsSpeculativeSemanticModel);
 
        var document2 = document1.WithText(SourceText.From("""
            class C
                sub M()
                    return nothing
                end sub
            end class
            """));
 
        // This should be able to get a speculative model using the original model we primed the cache with.
        var model2 = await document2.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.True(model2.IsSpeculativeSemanticModel);
 
        // Ensure this doesn't crash.
        VisualBasicSyntaxContext.CreateContext(document2, model2, source.IndexOf("sub"), CancellationToken.None);
    }
 
    [Fact, WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1541001")]
    [WorkItem("https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1587699")]
    public async Task TestOutOfBoundsInSyntaxContext2_VisualBasic()
    {
        var source = """
            class C
                sub M()
                    return
                end sub
            end class
            """;
        var document1 = CreateDocument(source, LanguageNames.VisualBasic);
 
        // First call will prime the cache to point at the real semantic model.
        var model1 = await document1.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.False(model1.IsSpeculativeSemanticModel);
 
        var document2 = document1.WithText(SourceText.From("""
            class C
            class C
                                                sub M()
                    return nothing
                end sub
            end class
            """));
 
        // Because the change in trivia shifted the method definition, we are not able to get a speculative model based on previous model
        var model2 = await document2.ReuseExistingSpeculativeModelAsync(source.IndexOf("return"), CancellationToken.None);
        Assert.False(model2.IsSpeculativeSemanticModel);
 
        // Ensure this doesn't crash.
        VisualBasicSyntaxContext.CreateContext(document2, model2, source.IndexOf("return"), CancellationToken.None);
    }
 
    #endregion
}