Resolving members with CSHarpTypeResolveContext #507

Open
opened 2015-12-15 10:17:57 -06:00 by W3L-Sebastian-Heimfarth · 0 comments
W3L-Sebastian-Heimfarth commented 2015-12-15 10:17:57 -06:00 (Migrated from github.com)

Hi!

We are using NRefactory for c# code completion and most times it works great :) Recently we found a problem which we think is caused by not finding the correct CSHarpTypeResolveContext. For a given code snippet and a given cursorPosition we calculate the CSHarpTypeResolveContext as follows (this is a simplified version of our production code):

public class TypeResolveContextDetermination
    {
        /// <summary>
        /// Resolves TypeResolveContext like this:
        /// (single) main-assembly -> current usingScope -> innermostTypeDefinition -> currentMember (e.g. Method, Ctor, etc.)
        /// </summary>
        /// <param name="sourceCode"></param>
        /// <param name="cursorIndex"></param>
        /// <returns></returns>
        public CSharpTypeResolveContext DetermineTypeResolveContext(string sourceCode, int cursorIndex)
        {
            var document = new ReadOnlyDocument(sourceCode);
            IProjectContent projectContent;
            CSharpUnresolvedFile unresolvedFile;

            var compilation = CreateCompilation(sourceCode, out projectContent, out unresolvedFile);
            var cursorLocation = cursorIndex > 0 ? document.GetLocation(cursorIndex) : new TextLocation(1, 1);


            var typeResolveContext = new CSharpTypeResolveContext(compilation.MainAssembly);
            typeResolveContext = typeResolveContext.WithUsingScope(unresolvedFile.GetUsingScope(cursorLocation).Resolve(compilation));

            var currentTypeDefinition = unresolvedFile.GetInnermostTypeDefinition(cursorLocation);
            if (currentTypeDefinition != null)
            {
                var resolvedDef = currentTypeDefinition.Resolve(typeResolveContext).GetDefinition();
                typeResolveContext = typeResolveContext.WithCurrentTypeDefinition(resolvedDef);
                var curMember =
                    resolvedDef.Members.FirstOrDefault(m => m.Region.Begin <= cursorLocation && cursorLocation < m.BodyRegion.End);
                if (curMember != null) // for testcase CodeB curMember is null, despite resolvedDef.Members contains MethodA (see tests at the bottom)
                {
                    typeResolveContext = typeResolveContext.WithCurrentMember(curMember);
                }
            }

            return typeResolveContext;
        }

        private ICompilation CreateCompilation(string sourceCode, out IProjectContent projectContent, out CSharpUnresolvedFile unresolvedFile)
        {
            projectContent = new CSharpProjectContent();
            projectContent = AddFileToProjectContent(projectContent, sourceCode, "FileOfInterest.cs", out unresolvedFile);

            projectContent.AddAssemblyReferences(new DefaultAssemblyReference(typeof(List<>).Assembly.GetName().Name));

            var compilation = projectContent.CreateCompilation();
            return compilation;
        }

        private IProjectContent AddFileToProjectContent(
            IProjectContent projectContent,
            string sourceCode,
            string fileName,
            out CSharpUnresolvedFile unresolvedFile)
        {
            SyntaxTree syntaxTree = new CSharpParser().Parse(sourceCode, fileName);
            syntaxTree.Freeze();

            unresolvedFile = syntaxTree.ToTypeSystem();
            return projectContent.AddOrUpdateFiles(unresolvedFile);
        }
    }

We think, the code above should satisfy the following four test cases. But for code snippet CodeB we can not determine the correct context.

namespace NRefactoryTests
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using ICSharpCode.NRefactory;
    using ICSharpCode.NRefactory.CSharp;
    using ICSharpCode.NRefactory.CSharp.TypeSystem;
    using ICSharpCode.NRefactory.Editor;
    using ICSharpCode.NRefactory.TypeSystem;
    using ICSharpCode.NRefactory.TypeSystem.Implementation;
    using NUnit.Framework;

    public class TypeContextTest
    {
        private TypeResolveContextDetermination typeResolveContextDetermination;

        private const string CodeA = 
@"namespace TestNamespace
{
               public class TestClass 
                {
                              public virtual void MethodA()
                              {
                                              TestClass test = new TestClass();
                                               this.$$$
                                               TestClass test2 = new TestClass();
                              }

                               public virtual void MethodB()
                              {

                               }
               }
}";

        private const string CodeB =
@"namespace TestNamespace
{
               public class TestClass 
                {
                              public virtual void MethodA()
                              {
                                              TestClass test = new TestClass();
                                               this.M$$$
                                               TestClass test2 = new TestClass();
                              }

                               public virtual void MethodB()
                              {

                               }
               }
}";

        private const string CodeC =
@"namespace TestNamespace
{
               public class TestClass 
                {
                              public virtual void MethodA()
                              {
                                              TestClass test = new TestClass();
                                               this.M$$$
                              }

                               public virtual void MethodB()
                              {

                               }
               }
}";

        private const string CodeD =
@"namespace TestNamespace
{
               public class TestClass 
                {
                              public virtual void MethodA()
                              {
                                               this.M$$$
                                               TestClass test2 = new TestClass();
                              }

                               public virtual void MethodB()
                              {

                               }
               }
}";


        [SetUp]
        public void Setup()
        {
            typeResolveContextDetermination = new TypeResolveContextDetermination();
        }

        [Test]
        public void ResolveTypeContextWithNRefactory_ContextShouldBeMethodA(
            [Values(CodeA, CodeB, CodeC, CodeD)] string code)
        {
            int cursorPosition = code.IndexOf("$$$", StringComparison.Ordinal);
            code = code.Replace("$$$", string.Empty);

            var context = typeResolveContextDetermination.DetermineTypeResolveContext(code, cursorPosition);

            Assert.NotNull(context);
            Assert.NotNull(context.CurrentMember, "CurrentMember of the context should be MethodA");
            Assert.AreEqual("TestNamespace.TestClass.MethodA", context.CurrentMember.FullName, "CurrentMember of the context should be MethodA");
        }
    }

The problem with CodeB is, that we can not find a member:

var curMember =
                    resolvedDef.Members.FirstOrDefault(m => m.Region.Begin <= cursorLocation && cursorLocation < m.BodyRegion.End);

Is this actually a bug or are we doing something wrong?

Thanks for your help!

Hi! We are using NRefactory for c# code completion and most times it works great :) Recently we found a problem which we think is caused by not finding the correct `CSHarpTypeResolveContext`. For a given code snippet and a given cursorPosition we calculate the `CSHarpTypeResolveContext` as follows (this is a simplified version of our production code): ``` csharp public class TypeResolveContextDetermination { /// <summary> /// Resolves TypeResolveContext like this: /// (single) main-assembly -> current usingScope -> innermostTypeDefinition -> currentMember (e.g. Method, Ctor, etc.) /// </summary> /// <param name="sourceCode"></param> /// <param name="cursorIndex"></param> /// <returns></returns> public CSharpTypeResolveContext DetermineTypeResolveContext(string sourceCode, int cursorIndex) { var document = new ReadOnlyDocument(sourceCode); IProjectContent projectContent; CSharpUnresolvedFile unresolvedFile; var compilation = CreateCompilation(sourceCode, out projectContent, out unresolvedFile); var cursorLocation = cursorIndex > 0 ? document.GetLocation(cursorIndex) : new TextLocation(1, 1); var typeResolveContext = new CSharpTypeResolveContext(compilation.MainAssembly); typeResolveContext = typeResolveContext.WithUsingScope(unresolvedFile.GetUsingScope(cursorLocation).Resolve(compilation)); var currentTypeDefinition = unresolvedFile.GetInnermostTypeDefinition(cursorLocation); if (currentTypeDefinition != null) { var resolvedDef = currentTypeDefinition.Resolve(typeResolveContext).GetDefinition(); typeResolveContext = typeResolveContext.WithCurrentTypeDefinition(resolvedDef); var curMember = resolvedDef.Members.FirstOrDefault(m => m.Region.Begin <= cursorLocation && cursorLocation < m.BodyRegion.End); if (curMember != null) // for testcase CodeB curMember is null, despite resolvedDef.Members contains MethodA (see tests at the bottom) { typeResolveContext = typeResolveContext.WithCurrentMember(curMember); } } return typeResolveContext; } private ICompilation CreateCompilation(string sourceCode, out IProjectContent projectContent, out CSharpUnresolvedFile unresolvedFile) { projectContent = new CSharpProjectContent(); projectContent = AddFileToProjectContent(projectContent, sourceCode, "FileOfInterest.cs", out unresolvedFile); projectContent.AddAssemblyReferences(new DefaultAssemblyReference(typeof(List<>).Assembly.GetName().Name)); var compilation = projectContent.CreateCompilation(); return compilation; } private IProjectContent AddFileToProjectContent( IProjectContent projectContent, string sourceCode, string fileName, out CSharpUnresolvedFile unresolvedFile) { SyntaxTree syntaxTree = new CSharpParser().Parse(sourceCode, fileName); syntaxTree.Freeze(); unresolvedFile = syntaxTree.ToTypeSystem(); return projectContent.AddOrUpdateFiles(unresolvedFile); } } ``` We think, the code above should satisfy the following four test cases. But for code snippet `CodeB` we can not determine the correct context. ``` csharp namespace NRefactoryTests { using System; using System.Collections.Generic; using System.Linq; using ICSharpCode.NRefactory; using ICSharpCode.NRefactory.CSharp; using ICSharpCode.NRefactory.CSharp.TypeSystem; using ICSharpCode.NRefactory.Editor; using ICSharpCode.NRefactory.TypeSystem; using ICSharpCode.NRefactory.TypeSystem.Implementation; using NUnit.Framework; public class TypeContextTest { private TypeResolveContextDetermination typeResolveContextDetermination; private const string CodeA = @"namespace TestNamespace { public class TestClass { public virtual void MethodA() { TestClass test = new TestClass(); this.$$$ TestClass test2 = new TestClass(); } public virtual void MethodB() { } } }"; private const string CodeB = @"namespace TestNamespace { public class TestClass { public virtual void MethodA() { TestClass test = new TestClass(); this.M$$$ TestClass test2 = new TestClass(); } public virtual void MethodB() { } } }"; private const string CodeC = @"namespace TestNamespace { public class TestClass { public virtual void MethodA() { TestClass test = new TestClass(); this.M$$$ } public virtual void MethodB() { } } }"; private const string CodeD = @"namespace TestNamespace { public class TestClass { public virtual void MethodA() { this.M$$$ TestClass test2 = new TestClass(); } public virtual void MethodB() { } } }"; [SetUp] public void Setup() { typeResolveContextDetermination = new TypeResolveContextDetermination(); } [Test] public void ResolveTypeContextWithNRefactory_ContextShouldBeMethodA( [Values(CodeA, CodeB, CodeC, CodeD)] string code) { int cursorPosition = code.IndexOf("$$$", StringComparison.Ordinal); code = code.Replace("$$$", string.Empty); var context = typeResolveContextDetermination.DetermineTypeResolveContext(code, cursorPosition); Assert.NotNull(context); Assert.NotNull(context.CurrentMember, "CurrentMember of the context should be MethodA"); Assert.AreEqual("TestNamespace.TestClass.MethodA", context.CurrentMember.FullName, "CurrentMember of the context should be MethodA"); } } ``` The problem with CodeB is, that we can not find a member: ``` csharp var curMember = resolvedDef.Members.FirstOrDefault(m => m.Region.Begin <= cursorLocation && cursorLocation < m.BodyRegion.End); ``` Is this actually a bug or are we doing something wrong? Thanks for your help!
This discussion has been locked. Commenting is limited to contributors.
No milestone
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
gh-icsharpcode/NRefactory#507
No description provided.