2

我正在为 Resharper 编写一个导航插件,我的情况是我有一个IDeclaredElement我从中获得的列表

var declaredElements = context.GetData(DataConstants.DECLARED_ELEMENTS)

此元素是用户将鼠标光标置于其上的元素。

我想做的是获取IDeclaredType声明元素的 ,包括它可能具有的任何类型参数,如果它是泛型类型。

resharper SDK 文档在涉及类型系统时非常简单,根本没有真正解释各种类型之间的关系。

我一直在寻找其他插件以尝试找到这方面的示例,但结果都是空的。我已经检查了每一个 Util 和 Extension 类,看看是否有一个方法可以给我想要的东西,但是没有。

我发现的唯一一件事是:

declaredElements.First().GetSuperTypes()

它返回类型层次结构,不包括当前类型。有用,但不是我想要的。

是否有人对此 API 有任何经验或了解它的工作原理?我喜欢一个能更多解释类型之间关系的答案。

我对它的理解,简单来说:

  • 名称中带有Declared(IDeclaredElement, IDeclaredType)的类型 似乎是指物理代码元素。
  • IType 似乎是所有类型的顶级接口,与物理代码元素不对应
  • 我不清楚名称中带有 Element 的类型的含义(ITypeElement, IDeclaredElement),也许它指的是 AST 元素。

我希望对此进行一些澄清。

4

2 回答 2

9

我在文档中添加了一个问题以更新此问题:https ://github.com/JetBrains/resharper-devguide/issues/4

我将尝试在这里提供一个盆栽解释。

类型层次结构定义了代码的ITreeNode抽象语法树。这提供了很多信息,但是非常底层——它直接映射到代码的原始文本。它还遗漏了一些更高级别的信息。例如,如果我想获取类声明的所有类型成员,我可以遍历该类的 AST,并收集所有适当的树节点,但我还必须处理部分类,而 AST 不提供任何信息用于定位班级的其他部分。同样,如果我看到类声明public class Foo : Bar,我将不得不手动解析Bar基类型。

IDeclaredElement类型层次本质上是语法树的语义视图。在最简单的层面上,声明的元素是“具有声明的东西”。这可以是类声明,或方法声明,甚至与代码无关的东西——HTML 元素、CSS 类甚至颜色和文件系统路径(这就是它被称为“元素”的原因——它需要一个可以适用于很多不同的东西)。

例如,CLR 类型用ITypeElement接口表示,该接口派生自IDeclaredElement. 它为获取目标类型的方法、属性、构造函数等的声明元素提供了方法和属性。因此,(几乎)可以仅根据声明的元素来提供 CLR 源项目的语义视图。差不多,但不完全。

已声明元素具有GetDeclarations提供IDeclaration语法树节点的方法,这些节点是已声明元素的声明。类似地,IDeclaration节点提供了DeclaredElement能够从节点获取已声明元素的属性。

此外,ReSharper 有一个非常强大的机制,称为引用,它允许树节点具有将解析为已声明元素的传出引用(它也可能无法解析,这是一个错误,例如使用尚未写入,或者它可以解析为多个元素,例如使用不限定它是哪个重载的方法)。这些引用可以应用于任何节点,例如引用变量声明的变量名,或者引用声明的元素Bar(从中可以获得 的和源代码)。public class Foo : BarBarIDeclarationBar

这提供了一组令人印象深刻的特性——代码文件的语法视图、代码声明的语义视图以及将所有内容连接在一起的引用,但这并没有涵盖所有内容。已声明的元素提供了已声明事物的语义视图,但并不打算代表所有使用场景。

具体来说(查看 CLR 类型),它不能将类型的用法表示为数组、指针或封闭的泛型类型。ITypeElement可以提供类Fooor的语义视图Bar<T>,但不能表示Foo[], or Bar<Quux>

声明的元素需要能够将这些使用场景建模为基类、方法签名等。为此,派生的声明元素(例如ITypeElement)使用附加的接口层次结构来表示这种“类型系统”信息。这种层次结构取决于被分析的语言。对于 CLR 类型,它是IType层次结构,对于 JavaScript,它是IJavaScriptType.

IType是附加信息,而不是替换声明元素的语义视图。可以返回所有类型成员的IType符号表,但不提供与此相同的访问ITypeElement器。相反,(取决于所建模的内容)anIType本质上是一个声明元素和 的实例的包装器ISubstitution,它提供了对泛型类型参数的替换(数组表示为System.Array类型,具有底层元素类型,它本身就是IType,因为它可能是一个封闭的泛型或另一个数组)。替换也可以是空替换,它不替换任何内容,允许表示为开放泛型的类型或根本不是泛型的类型。该IDeclaredType接口是一个IType指的是已声明的元素。

旁白:解析引用实际上解析为一个声明的元素和一个ISubstitution,同样,模型泛型。在解析方法声明签名的引用时,您需要知道它是一个IList<T>,以及它是什么T

为了获得一个IType实例,您要么需要从现有声明的元素(方法签名、基类等)中获取一个,要么通过使用TypeFactory.CreateType. ISubstitution如果它是泛型类型,您很可能还需要指定一个。您还可以通过以下方式获得一堆常见的“预定义”类型:

var type = psiModule.GetPredefinedType(context).String; 

您可以使用这些类型传递给其中一种TypeFactory.CreateType方法,以充当ITypeElement您也传递的类型参数。

所以,结果是,我们在源代码中声明了一个类,这给了我们ITreeNode,IDeclarationITypeDeclaration. 我们可以使用IDeclaration, 或解析引用来获取此声明的语义视图,IDeclaredElement,ITypeElement是表示类的派生接口。基于 CLR 的声明元素用于IType表示类型使用,例如可能需要是封闭泛型的基类,或可能是开放泛型或数组的方法参数。IDeclaredType是一种类型用法,可以让我们回到已声明的元素。并且类型通常在内部用声明的元素和 an 表示ISubstitution,当没有泛型参数时,它可以填充任何泛型参数,或者是 ID 替换。最后,您可以IType使用TypeFactory.CreateType或使用PredefinedType.

于 2014-11-11T14:31:26.160 回答
3

看看这段代码:

void Do(IDataContext dataContext)
{
  foreach (var reference in dataContext.GetData(DataConstants.REFERENCES))
  {
    var resolveResultWithInfo = reference.Resolve().Result;
    var typeElement = resolveResultWithInfo.DeclaredElement as ITypeElement;
    if (typeElement != null)
    {
      var substitution = resolveResultWithInfo.Substitution;
      var declaredType = TypeFactory.CreateType(typeElement, substitution);
    }
  }
}
于 2014-11-09T11:13:37.123 回答