From 10d26b5f780c898aac17f48337e15cde41a2167b Mon Sep 17 00:00:00 2001 From: Binto86 <76395771+Binto86@users.noreply.github.com> Date: Tue, 5 Sep 2023 07:41:31 +0200 Subject: [PATCH] Documentation comments from metadata (#290) * Added XmlDocument to MetadataAssemblySymbol * Updated the way documentation is passed to MetadataReference * First docs from metadata test * Documentation for types works * Documentation is loaded for most types of symbols * More structure work * Generics work for types * Added edge case test * Generics work * Started on syntax tree for documentation * Added parameters * Added structured documentation to Symbol * Work on XmlDocumentationExtractor * Returning XElement instead of string * Added paramref * Added code tag * Updated formating * Update DocumentationCommentsTests.cs * Links work * Fixes * Update DocumentationCommentsTests.cs * Type parametrs support * Added tests for type params * Update XmlDocumentationExtractor.cs * PR comments * Added markdown documentation structure * After merge cleanup * Added documentation * cleanup * PR comments * sections are not responsible for generating their own markdown and xml * Remove most of sections * Update src/Draco.Compiler/Internal/Symbols/Metadata/MetadataMethodSymbol.cs Co-authored-by: LPeter1997 * Update MetadataMethodSymbol.cs * Added PrefixedDocumentationFullName * Removed paramref and typeparamref as separate nodes * Removed RawDocumentation * PR comments * Refactor XmlDocumentationExtractor * PR comments * PR comments * PR comments * Update XmlDocumentationExtractor.cs * Update src/Draco.Compiler/Internal/Symbols/Synthetized/PrimitiveTypeSymbol.cs Co-authored-by: LPeter1997 * PR comments * Update SourceModuleSymbol.cs * Update src/Draco.Compiler/Internal/Symbols/Source/SourceModuleSymbol.cs Co-authored-by: LPeter1997 * PR comments * Update src/Draco.Compiler/Internal/Symbols/Metadata/MetadataSymbol.cs Co-authored-by: LPeter1997 * PR comments * PR comments * Update src/Draco.Compiler/Internal/Symbols/Metadata/MetadataSymbol.cs Co-authored-by: LPeter1997 * Update src/Draco.Compiler/Internal/Symbols/Metadata/MetadataSymbol.cs Co-authored-by: LPeter1997 * PR comments * PR comments * Last cleanup --------- Co-authored-by: LPeter1997 --- .../Semantics/DocumentationCommentsTests.cs | 593 +++++++++++++++++- .../Semantics/TypeCheckingTests.cs | 42 ++ src/Draco.Compiler.Tests/TestUtilities.cs | 9 +- src/Draco.Compiler/Api/Compilation.cs | 2 +- src/Draco.Compiler/Api/MetadataReference.cs | 22 +- src/Draco.Compiler/Api/Semantics/Symbol.cs | 2 +- .../Documentation/DocumentationElement.cs | 130 ++++ .../Documentation/DocumentationSection.cs | 59 ++ .../MarkdownDocumentationExtractor.cs | 31 + .../Extractors/XmlDocumentationExtractor.cs | 116 ++++ .../Documentation/SymbolDocumentation.cs | 126 ++++ .../Metadata/MetadataAssemblySymbol.cs | 10 +- .../Symbols/Metadata/MetadataFieldSymbol.cs | 14 + .../Symbols/Metadata/MetadataMethodSymbol.cs | 14 + .../Metadata/MetadataNamespaceSymbol.cs | 24 + .../Metadata/MetadataPropertySymbol.cs | 14 + .../Metadata/MetadataStaticClassSymbol.cs | 14 + .../Symbols/Metadata/MetadataSymbol.cs | 73 ++- .../Symbols/Metadata/MetadataTypeSymbol.cs | 14 + .../Symbols/Source/SourceFunctionSymbol.cs | 10 +- .../Symbols/Source/SourceGlobalSymbol.cs | 10 +- .../Symbols/Source/SourceLocalSymbol.cs | 10 +- .../Symbols/Source/SourceModuleSymbol.cs | 21 +- src/Draco.Compiler/Internal/Symbols/Symbol.cs | 28 +- .../MetadataBackedPrimitiveTypeSymbol.cs | 5 +- .../Internal/Symbols/TypeParameterSymbol.cs | 2 +- .../Internal/Symbols/TypeVariable.cs | 1 - 27 files changed, 1367 insertions(+), 29 deletions(-) create mode 100644 src/Draco.Compiler/Internal/Documentation/DocumentationElement.cs create mode 100644 src/Draco.Compiler/Internal/Documentation/DocumentationSection.cs create mode 100644 src/Draco.Compiler/Internal/Documentation/Extractors/MarkdownDocumentationExtractor.cs create mode 100644 src/Draco.Compiler/Internal/Documentation/Extractors/XmlDocumentationExtractor.cs create mode 100644 src/Draco.Compiler/Internal/Documentation/SymbolDocumentation.cs diff --git a/src/Draco.Compiler.Tests/Semantics/DocumentationCommentsTests.cs b/src/Draco.Compiler.Tests/Semantics/DocumentationCommentsTests.cs index 276d72806..00d157cd7 100644 --- a/src/Draco.Compiler.Tests/Semantics/DocumentationCommentsTests.cs +++ b/src/Draco.Compiler.Tests/Semantics/DocumentationCommentsTests.cs @@ -1,11 +1,54 @@ +using System.Collections.Immutable; +using System.Text; +using System.Xml; +using System.Xml.Linq; +using Draco.Compiler.Api; using Draco.Compiler.Api.Syntax; using Draco.Compiler.Internal.Symbols; using static Draco.Compiler.Api.Syntax.SyntaxFactory; +using static Draco.Compiler.Tests.TestUtilities; namespace Draco.Compiler.Tests.Semantics; public sealed class DocumentationCommentsTests : SemanticTestsBase { + private static string CreateXmlDocComment(string originalXml) + { + var result = new StringBuilder(); + originalXml = originalXml.ReplaceLineEndings("\n"); + foreach (var line in originalXml.Split('\n')) + { + result.Append($"/// {line}{Environment.NewLine}"); + } + return result.ToString(); + } + + private static string AddDocumentationTag(string insideXml) => $""" + + {insideXml} + + """; + + private static string PrettyXml(XElement element) + { + var stringBuilder = new StringBuilder(); + + var settings = new XmlWriterSettings() + { + OmitXmlDeclaration = true, + Indent = true, + IndentChars = string.Empty, + NewLineOnAttributes = false, + }; + + using (var xmlWriter = XmlWriter.Create(stringBuilder, settings)) + { + element.Save(xmlWriter); + } + + return stringBuilder.ToString(); + } + [Theory] [InlineData("This is doc comment")] [InlineData(""" @@ -36,7 +79,7 @@ public void FunctionDocumentationComment(string docComment) // Assert Assert.Empty(semanticModel.Diagnostics); - Assert.Equal(docComment, funcSym.Documentation); + Assert.Equal(docComment, funcSym.Documentation.ToMarkdown()); } [Theory] @@ -68,7 +111,7 @@ public void VariableDocumentationComment(string docComment) // Assert Assert.Empty(semanticModel.Diagnostics); - Assert.Equal(docComment, xSym.Documentation); + Assert.Equal(docComment, xSym.Documentation.ToMarkdown()); } [Theory] @@ -104,6 +147,550 @@ public void LabelDocumentationComment(string docComment) // Assert Assert.Empty(semanticModel.Diagnostics); - Assert.Equal(string.Empty, labelSym.Documentation); + Assert.Equal(string.Empty, labelSym.Documentation.ToMarkdown()); + } + + [Theory] + [InlineData("This is doc comment")] + [InlineData(""" + This is + multiline doc comment + """)] + public void ModuleDocumentationComment(string docComment) + { + // /// This is doc comment + // module documentedModule{ + // public var Foo = 0; + // } + // + // func foo(){ + // var x = documentedModule.Foo; + // } + + // Arrange + var tree = SyntaxTree.Create(CompilationUnit( + WithDocumentation(ModuleDeclaration( + "documentedModule", + VariableDeclaration(Api.Semantics.Visibility.Public, "Foo", null, LiteralExpression(0))), + docComment), + FunctionDeclaration( + "foo", + ParameterList(), + null, + BlockFunctionBody( + DeclarationStatement(VariableDeclaration("x", null, MemberExpression(NameExpression("documentedModule"), "Foo"))))))); + + var moduleRef = tree.FindInChildren(0).Accessed; + + // Act + var compilation = CreateCompilation(tree); + var semanticModel = compilation.GetSemanticModel(tree); + + var moduleSym = GetInternalSymbol(semanticModel.GetReferencedSymbol(moduleRef)); + + // Assert + Assert.Empty(semanticModel.Diagnostics); + Assert.Equal(docComment, moduleSym.Documentation.ToMarkdown()); + } + + [Fact] + public void TypeDocumentationFromMetadata() + { + // func main() { + // TestClass(); + // } + + // Arrange + var tree = SyntaxTree.Create(CompilationUnit(FunctionDeclaration( + "main", + ParameterList(), + null, + BlockFunctionBody(ExpressionStatement(CallExpression(NameExpression("TestClass"))))))); + + var docs = " Documentation for TestClass "; + + var xmlStream = new MemoryStream(); + + var testRef = CompileCSharpToMetadataRef($$""" + /// {{docs}} + public class TestClass { } + """, xmlStream: xmlStream).WithDocumentation(xmlStream); + + var call = tree.FindInChildren(0); + + // Act + var compilation = Compilation.Create( + syntaxTrees: ImmutableArray.Create(tree), + metadataReferences: Basic.Reference.Assemblies.Net70.ReferenceInfos.All + .Select(r => MetadataReference.FromPeStream(new MemoryStream(r.ImageBytes))) + .Append(testRef) + .ToImmutableArray()); + var semanticModel = compilation.GetSemanticModel(tree); + + var typeSym = GetInternalSymbol(semanticModel.GetReferencedSymbol(call)).ReturnType; + + // Assert + Assert.Empty(semanticModel.Diagnostics); + Assert.Equal(AddDocumentationTag(docs), PrettyXml(typeSym.Documentation.ToXml()), ignoreLineEndingDifferences: true); + } + + [Fact] + public void NestedTypeDocumentationFromMetadata() + { + // func main() { + // TestClass(); + // } + + // Arrange + var tree = SyntaxTree.Create(CompilationUnit(FunctionDeclaration( + "main", + ParameterList(), + null, + BlockFunctionBody(ExpressionStatement(CallExpression(NameExpression("TestClass"))))))); + + var docs = " Documentation for NestedTestClass "; + + var xmlStream = new MemoryStream(); + + var testRef = CompileCSharpToMetadataRef($$""" + public class TestClass + { + /// {{docs}} + public class NestedTestClass { } + } + """, xmlStream: xmlStream).WithDocumentation(xmlStream); + + var call = tree.FindInChildren(0); + + // Act + var compilation = Compilation.Create( + syntaxTrees: ImmutableArray.Create(tree), + metadataReferences: Basic.Reference.Assemblies.Net70.ReferenceInfos.All + .Select(r => MetadataReference.FromPeStream(new MemoryStream(r.ImageBytes))) + .Append(testRef) + .ToImmutableArray()); + var semanticModel = compilation.GetSemanticModel(tree); + + var typeSym = GetInternalSymbol(semanticModel.GetReferencedSymbol(call)).ReturnType; + var nestedTypeSym = GetMemberSymbol(typeSym, "NestedTestClass"); + + // Assert + Assert.Empty(semanticModel.Diagnostics); + Assert.Equal(AddDocumentationTag(docs), PrettyXml(nestedTypeSym.Documentation.ToXml()), ignoreLineEndingDifferences: true); + } + + [Fact] + public void StaticTypeDocumentationFromMetadata() + { + // func main() { + // var x = TestClass.foo; + // } + + // Arrange + var tree = SyntaxTree.Create(CompilationUnit(FunctionDeclaration( + "main", + ParameterList(), + null, + BlockFunctionBody(DeclarationStatement(VariableDeclaration("x", null, MemberExpression(NameExpression("TestClass"), "foo"))))))); + + var docs = " Documentation for TestClass "; + + var xmlStream = new MemoryStream(); + + var testRef = CompileCSharpToMetadataRef($$""" + /// {{docs}} + public static class TestClass + { + // Just so i can use it in draco + public static int foo = 0; + } + """, xmlStream: xmlStream).WithDocumentation(xmlStream); + + var @class = tree.FindInChildren(0).Accessed; + + // Act + var compilation = Compilation.Create( + syntaxTrees: ImmutableArray.Create(tree), + metadataReferences: Basic.Reference.Assemblies.Net70.ReferenceInfos.All + .Select(r => MetadataReference.FromPeStream(new MemoryStream(r.ImageBytes))) + .Append(testRef) + .ToImmutableArray()); + var semanticModel = compilation.GetSemanticModel(tree); + + var typeSym = GetInternalSymbol(semanticModel.GetReferencedSymbol(@class)); + + // Assert + Assert.Empty(semanticModel.Diagnostics); + Assert.Equal(AddDocumentationTag(docs), PrettyXml(typeSym.Documentation.ToXml()), ignoreLineEndingDifferences: true); + } + + [Fact] + public void MethodDocumentationFromMetadata() + { + // func main() { + // TestClass(); + // } + + // Arrange + var tree = SyntaxTree.Create(CompilationUnit(FunctionDeclaration( + "main", + ParameterList(), + null, + BlockFunctionBody(ExpressionStatement(CallExpression(NameExpression("TestClass"))))))); + + var docs = " Documentation for TestMethod "; + + var xmlStream = new MemoryStream(); + + var testRef = CompileCSharpToMetadataRef($$""" + public class TestClass + { + /// {{docs}} + public void TestMethod(int arg1, string arg2) { } + } + """, xmlStream: xmlStream).WithDocumentation(xmlStream); + + var call = tree.FindInChildren(0); + + // Act + var compilation = Compilation.Create( + syntaxTrees: ImmutableArray.Create(tree), + metadataReferences: Basic.Reference.Assemblies.Net70.ReferenceInfos.All + .Select(r => MetadataReference.FromPeStream(new MemoryStream(r.ImageBytes))) + .Append(testRef) + .ToImmutableArray()); + var semanticModel = compilation.GetSemanticModel(tree); + + var typeSym = GetInternalSymbol(semanticModel.GetReferencedSymbol(call)).ReturnType; + var methodSym = GetMemberSymbol(typeSym, "TestMethod"); + + // Assert + Assert.Empty(semanticModel.Diagnostics); + Assert.Equal(AddDocumentationTag(docs), PrettyXml(methodSym.Documentation.ToXml()), ignoreLineEndingDifferences: true); + } + + [Fact] + public void NoParamsMethodDocumentationFromMetadata() + { + // func main() { + // TestClass(); + // } + + // Arrange + var tree = SyntaxTree.Create(CompilationUnit(FunctionDeclaration( + "main", + ParameterList(), + null, + BlockFunctionBody(ExpressionStatement(CallExpression(NameExpression("TestClass"))))))); + + var docs = " Documentation for TestMethod "; + + var xmlStream = new MemoryStream(); + + var testRef = CompileCSharpToMetadataRef($$""" + using System; + + public class TestClass + { + /// {{docs}} + public void TestMethod() { } + } + """, xmlStream: xmlStream).WithDocumentation(xmlStream); + + var call = tree.FindInChildren(0); + + // Act + var compilation = Compilation.Create( + syntaxTrees: ImmutableArray.Create(tree), + metadataReferences: Basic.Reference.Assemblies.Net70.ReferenceInfos.All + .Select(r => MetadataReference.FromPeStream(new MemoryStream(r.ImageBytes))) + .Append(testRef) + .ToImmutableArray()); + var semanticModel = compilation.GetSemanticModel(tree); + + var typeSym = GetInternalSymbol(semanticModel.GetReferencedSymbol(call)).ReturnType; + var methodSym = GetMemberSymbol(typeSym, "TestMethod"); + + // Assert + Assert.Empty(semanticModel.Diagnostics); + Assert.Equal(AddDocumentationTag(docs), PrettyXml(methodSym.Documentation.ToXml()), ignoreLineEndingDifferences: true); + } + + [Fact] + public void FieldDocumentationFromMetadata() + { + // func main() { + // TestClass(); + // } + + // Arrange + var tree = SyntaxTree.Create(CompilationUnit(FunctionDeclaration( + "main", + ParameterList(), + null, + BlockFunctionBody(ExpressionStatement(CallExpression(NameExpression("TestClass"))))))); + + var docs = " Documentation for TestField "; + + var xmlStream = new MemoryStream(); + + var testRef = CompileCSharpToMetadataRef($$""" + public class TestClass + { + /// {{docs}} + public int TestField = 5; + } + """, xmlStream: xmlStream).WithDocumentation(xmlStream); + + var call = tree.FindInChildren(0); + + // Act + var compilation = Compilation.Create( + syntaxTrees: ImmutableArray.Create(tree), + metadataReferences: Basic.Reference.Assemblies.Net70.ReferenceInfos.All + .Select(r => MetadataReference.FromPeStream(new MemoryStream(r.ImageBytes))) + .Append(testRef) + .ToImmutableArray()); + var semanticModel = compilation.GetSemanticModel(tree); + + var typeSym = GetInternalSymbol(semanticModel.GetReferencedSymbol(call)).ReturnType; + var fieldSym = GetMemberSymbol(typeSym, "TestField"); + + // Assert + Assert.Empty(semanticModel.Diagnostics); + Assert.Equal(AddDocumentationTag(docs), PrettyXml(fieldSym.Documentation.ToXml()), ignoreLineEndingDifferences: true); + } + + [Fact] + public void PropertyDocumentationFromMetadata() + { + // func main() { + // TestClass(); + // } + + // Arrange + var tree = SyntaxTree.Create(CompilationUnit(FunctionDeclaration( + "main", + ParameterList(), + null, + BlockFunctionBody(ExpressionStatement(CallExpression(NameExpression("TestClass"))))))); + + var docs = " Documentation for TestProperty "; + + var xmlStream = new MemoryStream(); + + var testRef = CompileCSharpToMetadataRef($$""" + public class TestClass + { + /// {{docs}} + public int TestProperty { get; } + } + """, xmlStream: xmlStream).WithDocumentation(xmlStream); + + var call = tree.FindInChildren(0); + + // Act + var compilation = Compilation.Create( + syntaxTrees: ImmutableArray.Create(tree), + metadataReferences: Basic.Reference.Assemblies.Net70.ReferenceInfos.All + .Select(r => MetadataReference.FromPeStream(new MemoryStream(r.ImageBytes))) + .Append(testRef) + .ToImmutableArray()); + var semanticModel = compilation.GetSemanticModel(tree); + + var typeSym = GetInternalSymbol(semanticModel.GetReferencedSymbol(call)).ReturnType; + var propertySym = GetMemberSymbol(typeSym, "TestProperty"); + + // Assert + Assert.Empty(semanticModel.Diagnostics); + Assert.Equal(AddDocumentationTag(docs), PrettyXml(propertySym.Documentation.ToXml()), ignoreLineEndingDifferences: true); + } + + [Fact] + public void GenericsDocumentationFromMetadata() + { + // func main() { + // TestClass(); + // } + + // Arrange + var tree = SyntaxTree.Create(CompilationUnit(FunctionDeclaration( + "main", + ParameterList(), + null, + BlockFunctionBody(ExpressionStatement(CallExpression(GenericExpression(NameExpression("TestClass"), NameType("int32")))))))); + + var classDocs = " Documentation for TestClass "; + var methodDocs = " Documentation for TestMethod "; + + var xmlStream = new MemoryStream(); + + var testRef = CompileCSharpToMetadataRef($$""" + using System; + + /// {{classDocs}} + public class TestClass + { + /// {{methodDocs}} + public void TestMethod(T arg1, T arg2, U arg3) { } + } + """, xmlStream: xmlStream).WithDocumentation(xmlStream); + + var call = tree.FindInChildren(0); + + // Act + var compilation = Compilation.Create( + syntaxTrees: ImmutableArray.Create(tree), + metadataReferences: Basic.Reference.Assemblies.Net70.ReferenceInfos.All + .Select(r => MetadataReference.FromPeStream(new MemoryStream(r.ImageBytes))) + .Append(testRef) + .ToImmutableArray()); + var semanticModel = compilation.GetSemanticModel(tree); + + var typeSym = GetInternalSymbol(semanticModel.GetReferencedSymbol(call)).ReturnType; + var methodSym = GetMemberSymbol(typeSym, "TestMethod"); + + // Assert + Assert.Empty(semanticModel.Diagnostics); + Assert.Equal(AddDocumentationTag(classDocs), PrettyXml(typeSym.GenericDefinition!.Documentation.ToXml()), ignoreLineEndingDifferences: true); + Assert.Equal(AddDocumentationTag(methodDocs), PrettyXml(methodSym.GenericDefinition!.Documentation.ToXml()), ignoreLineEndingDifferences: true); + } + + [Fact] + public void XmlDocumentationExtractorTest() + { + // import TestNamespace; + // func main() { + // TestClass(); + // } + + // Arrange + var tree = SyntaxTree.Create(CompilationUnit( + ImportDeclaration("TestNamespace"), + FunctionDeclaration( + "main", + ParameterList(), + null, + BlockFunctionBody(ExpressionStatement(CallExpression(NameExpression("TestClass"))))))); + + var originalDocs = """ + Documentation for TestMethod, which is in , random generic link + Documentation for arg1 + Documentation for arg2 + Useless type param + + var x = 0; + void Foo(int z) { } + + added to , is not used + """; + + var xmlStream = new MemoryStream(); + + var testRef = CompileCSharpToMetadataRef($$""" + namespace TestNamespace; + public class TestClass + { + {{CreateXmlDocComment(originalDocs)}} + public int TestMethod(int arg1, int arg2) => arg1 + arg2; + } + """, xmlStream: xmlStream).WithDocumentation(xmlStream); + + var call = tree.FindInChildren(0); + + // Act + var compilation = Compilation.Create( + syntaxTrees: ImmutableArray.Create(tree), + metadataReferences: Basic.Reference.Assemblies.Net70.ReferenceInfos.All + .Select(r => MetadataReference.FromPeStream(new MemoryStream(r.ImageBytes))) + .Append(testRef) + .ToImmutableArray()); + var semanticModel = compilation.GetSemanticModel(tree); + + var typeSym = GetInternalSymbol(semanticModel.GetReferencedSymbol(call)).ReturnType; + var methodSym = GetMemberSymbol(typeSym, "TestMethod"); + + var xmlGeneratedDocs = """ + Documentation for TestMethod, which is in , random generic link + Documentation for arg1 + Documentation for arg2 + Useless type param + var x = 0; + void Foo(int z) { } + + added to , is not used + """; + + var mdGeneratedDocs = """ + Documentation for TestMethod, which is in [TestNamespace.TestClass](), random generic link [System.Collections.Generic.List]() + # parameters + - arg1: Documentation for arg1 + - arg2: Documentation for arg2 + # type parameters + - T: Useless type param + ```cs + var x = 0; + void Foo(int z) { } + ``` + # returns + [arg1]() added to [arg2](), [T]() is not used + """; + + var resultXml = PrettyXml(methodSym.Documentation.ToXml()); + var resultMd = methodSym.Documentation.ToMarkdown(); + + // Assert + Assert.Empty(semanticModel.Diagnostics); + Assert.Equal(AddDocumentationTag(xmlGeneratedDocs), resultXml, ignoreLineEndingDifferences: true); + Assert.Equal(mdGeneratedDocs, resultMd, ignoreLineEndingDifferences: true); + } + + [Fact] + public void MarkdownDocumentationExtractorTest() + { + // Arrange + var originalDocs = """ + Documentation for TestMethod, which is in [TestNamespace.TestClass](), random generic link [System.Collections.Generic.List]() + # parameters + - arg1: Documentation for arg1 + - arg2: Documentation for arg2 + # type parameters + - T: Useless type param + ```cs + var x = 0; + void Foo(int z) { } + ``` + # returns + [arg1]() added to [arg2](), [T]() is not used + """; + + // /// documentation + // func TestMethod() { } + + var tree = SyntaxTree.Create(CompilationUnit( + WithDocumentation(FunctionDeclaration( + "TestMethod", + ParameterList(), + null, + BlockFunctionBody()), originalDocs))); + + var testMethodDecl = tree.FindInChildren(0); + + // Act + var compilation = Compilation.Create( + syntaxTrees: ImmutableArray.Create(tree), + metadataReferences: Basic.Reference.Assemblies.Net70.ReferenceInfos.All + .Select(r => MetadataReference.FromPeStream(new MemoryStream(r.ImageBytes))) + .ToImmutableArray()); + var semanticModel = compilation.GetSemanticModel(tree); + + var methodSym = GetInternalSymbol(semanticModel.GetDeclaredSymbol(testMethodDecl)); + + var resultMd = methodSym.Documentation.ToMarkdown(); + + // Assert + Assert.Empty(semanticModel.Diagnostics); + Assert.Equal(originalDocs, resultMd); + Assert.Equal(methodSym.Documentation.ToMarkdown(), resultMd); } } diff --git a/src/Draco.Compiler.Tests/Semantics/TypeCheckingTests.cs b/src/Draco.Compiler.Tests/Semantics/TypeCheckingTests.cs index 9c0e7657b..9ba838701 100644 --- a/src/Draco.Compiler.Tests/Semantics/TypeCheckingTests.cs +++ b/src/Draco.Compiler.Tests/Semantics/TypeCheckingTests.cs @@ -2179,4 +2179,46 @@ public void IndexerWitingOverloadedCall() Assert.Single(diags); AssertDiagnostic(diags, TypeCheckingErrors.TypeMismatch); } + + [Fact] + public void GettingTypeFromReference() + { + // func main(){ + // var x = FooModule.foo; + // } + + var main = SyntaxTree.Create(CompilationUnit( + FunctionDeclaration( + "main", + ParameterList(), + null, + BlockFunctionBody( + DeclarationStatement(VariableDeclaration("x", null, MemberExpression(NameExpression("FooModule"), "foo"))))))); + + var fooRef = CompileCSharpToMetadataRef(""" + using System; + public static class FooModule{ + public static Random foo; + } + """); + + var xDecl = main.FindInChildren(0); + + // Act + var compilation = Compilation.Create( + syntaxTrees: ImmutableArray.Create(main), + metadataReferences: Basic.Reference.Assemblies.Net70.ReferenceInfos.All + .Select(r => MetadataReference.FromPeStream(new MemoryStream(r.ImageBytes))) + .Append(fooRef) + .ToImmutableArray()); + + var semanticModel = compilation.GetSemanticModel(main); + + var diags = semanticModel.Diagnostics; + var xSym = GetInternalSymbol(semanticModel.GetDeclaredSymbol(xDecl)); + + // Assert + Assert.Empty(diags); + Assert.Equal("System.Random", xSym.Type.FullName); + } } diff --git a/src/Draco.Compiler.Tests/TestUtilities.cs b/src/Draco.Compiler.Tests/TestUtilities.cs index 2296f21b1..db26c1398 100644 --- a/src/Draco.Compiler.Tests/TestUtilities.cs +++ b/src/Draco.Compiler.Tests/TestUtilities.cs @@ -12,13 +12,13 @@ internal static class TestUtilities public static string ToPath(params string[] parts) => Path.GetFullPath(Path.Combine(parts)); - public static MetadataReference CompileCSharpToMetadataRef(string code, string assemblyName = DefaultAssemblyName, IEnumerable? aditionalReferences = null) + public static MetadataReference CompileCSharpToMetadataRef(string code, string assemblyName = DefaultAssemblyName, IEnumerable? aditionalReferences = null, Stream? xmlStream = null) { - var stream = CompileCSharpToStream(code, assemblyName, aditionalReferences); + var stream = CompileCSharpToStream(code, assemblyName, aditionalReferences, xmlStream); return MetadataReference.FromPeStream(stream); } - public static Stream CompileCSharpToStream(string code, string assemblyName = DefaultAssemblyName, IEnumerable? aditionalReferences = null) + public static Stream CompileCSharpToStream(string code, string assemblyName = DefaultAssemblyName, IEnumerable? aditionalReferences = null, Stream? xmlStream = null) { aditionalReferences ??= Enumerable.Empty(); var sourceText = SourceText.From(code, Encoding.UTF8); @@ -35,10 +35,11 @@ public static Stream CompileCSharpToStream(string code, string assemblyName = De options: new CSharpCompilationOptions(Microsoft.CodeAnalysis.OutputKind.DynamicallyLinkedLibrary)); var stream = new MemoryStream(); - var emitResult = compilation.Emit(stream); + var emitResult = compilation.Emit(stream, xmlDocumentationStream: xmlStream); Assert.True(emitResult.Success); stream.Position = 0; + if (xmlStream is not null) xmlStream.Position = 0; return stream; } } diff --git a/src/Draco.Compiler/Api/Compilation.cs b/src/Draco.Compiler/Api/Compilation.cs index 5473df3fd..ed086906f 100644 --- a/src/Draco.Compiler/Api/Compilation.cs +++ b/src/Draco.Compiler/Api/Compilation.cs @@ -334,7 +334,7 @@ internal Binder GetBinder(Symbol symbol) private ImmutableDictionary BuildMetadataAssemblies() => this.MetadataReferences .ToImmutableDictionary( r => r, - r => new MetadataAssemblySymbol(this, r.MetadataReader)); + r => new MetadataAssemblySymbol(this, r.MetadataReader, r.Documentation)); private ModuleSymbol BuildRootModule() => new MergedModuleSymbol( containingSymbol: null, name: string.Empty, diff --git a/src/Draco.Compiler/Api/MetadataReference.cs b/src/Draco.Compiler/Api/MetadataReference.cs index 927538420..824ae2aba 100644 --- a/src/Draco.Compiler/Api/MetadataReference.cs +++ b/src/Draco.Compiler/Api/MetadataReference.cs @@ -3,6 +3,7 @@ using System.Reflection; using System.Reflection.Metadata; using System.Reflection.PortableExecutable; +using System.Xml; namespace Draco.Compiler.Api; @@ -16,6 +17,11 @@ public abstract class MetadataReference /// public abstract MetadataReader MetadataReader { get; } + /// + /// The documentation for this reference. + /// + public abstract XmlDocument? Documentation { get; } + /// /// Creates a metadata reference from the given assembly. /// @@ -47,13 +53,27 @@ public static MetadataReference FromPeStream(Stream peStream) return new MetadataReaderReference(metadataReader); } + /// + /// Adds xml documentation to this metadata reference. + /// + /// The stream with the xml documentation. + /// New metadata reference containing xml documentation. + public MetadataReference WithDocumentation(Stream xmlStream) + { + var doc = new XmlDocument(); + doc.Load(xmlStream); + return new MetadataReaderReference(this.MetadataReader, doc); + } + private sealed class MetadataReaderReference : MetadataReference { public override MetadataReader MetadataReader { get; } + public override XmlDocument? Documentation { get; } - public MetadataReaderReference(MetadataReader metadataReader) + public MetadataReaderReference(MetadataReader metadataReader, XmlDocument? documentation = null) { this.MetadataReader = metadataReader; + this.Documentation = documentation; } } } diff --git a/src/Draco.Compiler/Api/Semantics/Symbol.cs b/src/Draco.Compiler/Api/Semantics/Symbol.cs index 38ed6b6c1..33e7d5e6d 100644 --- a/src/Draco.Compiler/Api/Semantics/Symbol.cs +++ b/src/Draco.Compiler/Api/Semantics/Symbol.cs @@ -176,7 +176,7 @@ internal abstract class SymbolBase : ISymbol public bool IsError => this.Symbol.IsError; public bool IsSpecialName => this.Symbol.IsSpecialName; public Location? Definition => this.Symbol.DeclaringSyntax?.Location; - public string Documentation => this.Symbol.Documentation; + public string Documentation => this.Symbol.Documentation.ToMarkdown(); public IEnumerable Members => this.Symbol.Members.Select(x => x.ToApiSymbol()); public SymbolBase(Symbol symbol) diff --git a/src/Draco.Compiler/Internal/Documentation/DocumentationElement.cs b/src/Draco.Compiler/Internal/Documentation/DocumentationElement.cs new file mode 100644 index 000000000..dab7a4a91 --- /dev/null +++ b/src/Draco.Compiler/Internal/Documentation/DocumentationElement.cs @@ -0,0 +1,130 @@ +using System.Collections.Immutable; +using System.Linq; +using System.Xml.Linq; +using Draco.Compiler.Internal.Symbols; +using Draco.Compiler.Internal.Symbols.Metadata; + +namespace Draco.Compiler.Internal.Documentation; + +/// +/// Represents single documentation element. +/// +internal abstract record class DocumentationElement +{ + /// + /// Creates a markdown representation of this documentation element. + /// + /// The documentation in markdown format. + public abstract string ToMarkdown(); + + /// + /// Creates an XML representation of this documentation element. + /// + /// The documentation in XML format. + public abstract XNode ToXml(); +} + +/// +/// Represents regular text inside documentation. +/// +/// The text represented by this element. +internal sealed record class TextDocumentationElement(string Text) : DocumentationElement +{ + public override string ToMarkdown() => this.Text; + + public override XText ToXml() => new XText(this.Text); +} + +/// +/// Any kind of symbol reference. +/// +/// The referenced symbol. +/// The inner s of this . +internal abstract record class SymbolDocumentationElement(Symbol? Symbol, ImmutableArray Elements) : DocumentationElement +{ + protected string Name => this.Symbol?.Name ?? string.Empty; + protected string? FilePath => this.Symbol?.DeclaringSyntax?.Location.SourceText.Path?.LocalPath; + // Note: For future when we will probably want to optionally return link to the param + public string Link => this.FilePath is null + ? string.Empty + : $"{this.FilePath}#L{this.Symbol?.DeclaringSyntax?.Location.Range?.Start.Line}"; + + public override string ToMarkdown() => $"- {this.Name}: {string.Join("", this.Elements.Select(x => x.ToMarkdown()))}"; +} + +/// +/// A single parameter. +/// +/// The parameter symbol. +/// The s that are contained in the description of this parameter. +internal sealed record class ParameterDocumentationElement(ParameterSymbol? Parameter, ImmutableArray Elements) : SymbolDocumentationElement(Parameter, Elements) +{ + public override XElement ToXml() => new XElement("param", + new XAttribute("name", this.Name), + this.Elements.Select(x => x.ToXml())); +} + +/// +/// A single type parameter. +/// +/// The type parameter symbol. +/// The s that are contained in the description of this type parameter. +internal sealed record class TypeParameterDocumentationElement(TypeParameterSymbol? TypeParameter, ImmutableArray Elements) : SymbolDocumentationElement(TypeParameter, Elements) +{ + public override XElement ToXml() => new XElement("typeparam", + new XAttribute("name", this.Name), + this.Elements.Select(x => x.ToXml())); +} + +/// +/// A link to some symbol in code. +/// +/// The symbol that is linked. +/// The text that should be displayed in the link. +internal sealed record class ReferenceDocumentationElement : DocumentationElement +{ + public Symbol? ReferencedSymbol { get; } + public string DisplayText { get; } + + private string? FilePath => this.ReferencedSymbol?.DeclaringSyntax?.Location.SourceText.Path?.LocalPath; + private string Link => this.FilePath is null + ? string.Empty + : $"{this.FilePath}#L{this.ReferencedSymbol?.DeclaringSyntax?.Location.Range?.Start.Line}"; + + public ReferenceDocumentationElement(Symbol? referencedSymbol, string? displayText = null) + { + this.ReferencedSymbol = referencedSymbol; + this.DisplayText = displayText ?? GetDisplayText(referencedSymbol); + } + + private static string GetDisplayText(Symbol? symbol) => symbol switch + { + ParameterSymbol or TypeParameterSymbol => symbol?.Name ?? string.Empty, + _ => symbol?.FullName ?? string.Empty, + }; + + public override string ToMarkdown() => $"[{this.DisplayText}]({this.Link})"; + + public override XElement ToXml() => this.ReferencedSymbol switch + { + ParameterSymbol => new XElement("paramref", new XAttribute("name", this.DisplayText)), + TypeParameterSymbol => new XElement("typeparamref", new XAttribute("name", this.DisplayText)), + _ => new XElement("see", new XAttribute("cref", MetadataSymbol.GetPrefixedDocumentationName(this.ReferencedSymbol))), + }; +} + +/// +/// Code element. +/// +/// The code. +/// The ID of the programming language this code is written in. +internal sealed record class CodeDocumentationElement(string Code, string Lang) : DocumentationElement +{ + public override string ToMarkdown() => $""" + ```{this.Lang} + {this.Code} + ``` + """; + + public override XNode ToXml() => new XElement("code", this.Code); +} diff --git a/src/Draco.Compiler/Internal/Documentation/DocumentationSection.cs b/src/Draco.Compiler/Internal/Documentation/DocumentationSection.cs new file mode 100644 index 000000000..d4abcb090 --- /dev/null +++ b/src/Draco.Compiler/Internal/Documentation/DocumentationSection.cs @@ -0,0 +1,59 @@ +using System.Collections.Immutable; + +namespace Draco.Compiler.Internal.Documentation; + +/// +/// Represents a section of the documentation. +/// +internal sealed class DocumentationSection +{ + public string Name { get; } + public SectionKind Kind { get; } + public ImmutableArray Elements { get; } + + private DocumentationSection(SectionKind kind, string name, ImmutableArray elements) + { + this.Kind = kind; + this.Name = name.ToLowerInvariant(); + this.Elements = elements; + } + + public DocumentationSection(SectionKind kind, ImmutableArray elements) + : this(kind, GetSectionName(kind), elements) + { + // NOTE: GetSectionName throws on Other + } + + public DocumentationSection(string name, ImmutableArray elements) + : this(GetSectionKind(name), name, elements) + { + } + + private static string GetSectionName(SectionKind kind) => kind switch + { + SectionKind.Summary => "summary", + SectionKind.Parameters => "parameters", + SectionKind.TypeParameters => "type parameters", + SectionKind.Code => "code", + _ => throw new System.ArgumentOutOfRangeException(nameof(kind)), + }; + + private static SectionKind GetSectionKind(string? name) => name switch + { + "summary" => SectionKind.Summary, + "parameters" => SectionKind.Parameters, + "type parameters" => SectionKind.TypeParameters, + "code" => SectionKind.Code, + _ => SectionKind.Other, + }; +} + +// Note: The values of the sections are used for ordering from smallest to highest +internal enum SectionKind +{ + Summary = 1, + Parameters = 2, + TypeParameters = 3, + Code = 4, + Other = 5, +} diff --git a/src/Draco.Compiler/Internal/Documentation/Extractors/MarkdownDocumentationExtractor.cs b/src/Draco.Compiler/Internal/Documentation/Extractors/MarkdownDocumentationExtractor.cs new file mode 100644 index 000000000..ed453fab7 --- /dev/null +++ b/src/Draco.Compiler/Internal/Documentation/Extractors/MarkdownDocumentationExtractor.cs @@ -0,0 +1,31 @@ +using Draco.Compiler.Internal.Symbols; + +namespace Draco.Compiler.Internal.Documentation.Extractors; + +/// +/// Extracts markdown into . +/// +internal sealed class MarkdownDocumentationExtractor +{ + /// + /// Extracts the markdown documentation from . + /// + /// The extracted markdown as . + public static SymbolDocumentation Extract(Symbol containingSymbol) => + new MarkdownDocumentationExtractor(containingSymbol.RawDocumentation, containingSymbol).Extract(); + + private readonly string markdown; + private readonly Symbol containingSymbol; + + private MarkdownDocumentationExtractor(string markdown, Symbol containingSymbol) + { + this.markdown = markdown; + this.containingSymbol = containingSymbol; + } + + /// + /// Extracts the . + /// + /// The extracted markdown as . + private SymbolDocumentation Extract() => new MarkdownSymbolDocumentation(this.markdown); +} diff --git a/src/Draco.Compiler/Internal/Documentation/Extractors/XmlDocumentationExtractor.cs b/src/Draco.Compiler/Internal/Documentation/Extractors/XmlDocumentationExtractor.cs new file mode 100644 index 000000000..587ea6f43 --- /dev/null +++ b/src/Draco.Compiler/Internal/Documentation/Extractors/XmlDocumentationExtractor.cs @@ -0,0 +1,116 @@ +using System.Collections.Immutable; +using System.Linq; +using System.Xml; +using Draco.Compiler.Internal.Symbols; +using Draco.Compiler.Internal.Symbols.Metadata; +using Draco.Compiler.Internal.Symbols.Synthetized; + +namespace Draco.Compiler.Internal.Documentation.Extractors; + +/// +/// Extracts XML into . +/// +internal sealed class XmlDocumentationExtractor +{ + /// + /// Extracts the xml documentation from . + /// + /// The extracted XMl as . + public static SymbolDocumentation Extract(Symbol containingSymbol) => + new XmlDocumentationExtractor(containingSymbol.RawDocumentation, containingSymbol).Extract(); + + private readonly string xml; + private readonly Symbol containingSymbol; + private MetadataAssemblySymbol Assembly => this.containingSymbol.AncestorChain.OfType().First(); + + private XmlDocumentationExtractor(string xml, Symbol containingSymbol) + { + this.xml = xml; + this.containingSymbol = containingSymbol; + } + + /// + /// Extracts the . + /// + /// The extracted XMl as . + private SymbolDocumentation Extract() + { + // TODO: exception + // para + // list + // c + // see - not cref links + // seealso + // b ? + // i ? + + var xml = $""" + + {this.xml} + + """; + var doc = new XmlDocument(); + doc.LoadXml(xml); + + var raw = doc.DocumentElement!.ChildNodes + .Cast() + .Select(this.ExtractSectionOrElement); + + var sections = raw.OfType().ToList(); + var elements = raw.OfType(); + + foreach (var grouped in elements.GroupBy(x => x.GetType())) + { + if (grouped.Key == typeof(ParameterDocumentationElement)) sections.Add(new DocumentationSection(SectionKind.Parameters, grouped.ToImmutableArray())); + else if (grouped.Key == typeof(TypeParameterDocumentationElement)) sections.Add(new DocumentationSection(SectionKind.TypeParameters, grouped.ToImmutableArray())); + } + return new SymbolDocumentation(sections.ToImmutableArray()); + } + + private object ExtractSectionOrElement(XmlNode node) => node.Name switch + { + "param" => new ParameterDocumentationElement(this.GetParameter(node.Attributes?["name"]?.Value ?? string.Empty), this.ExtractElementsFromNode(node)), + "typeparam" => new TypeParameterDocumentationElement(this.GetTypeParameter(node.Attributes?["name"]?.Value ?? string.Empty), this.ExtractElementsFromNode(node)), + "code" => new DocumentationSection(SectionKind.Code, ImmutableArray.Create(this.ExtractElement(node))), + "summary" => new DocumentationSection(SectionKind.Summary, this.ExtractElementsFromNode(node)), + _ => new DocumentationSection(node.Name, this.ExtractElementsFromNode(node)), + }; + + private DocumentationElement ExtractElement(XmlNode node) => node.LocalName switch + { + "#text" => new TextDocumentationElement(node.InnerText), + "see" => this.ConstructReference(node), + "paramref" => new ReferenceDocumentationElement(this.GetParameter(node.Attributes?["name"]?.Value ?? string.Empty)), + "typeparamref" => new ReferenceDocumentationElement(this.GetTypeParameter(node.Attributes?["name"]?.Value ?? string.Empty)), + "code" => new CodeDocumentationElement(node.InnerXml.Trim('\r', '\n'), "cs"), + _ => new TextDocumentationElement(node.InnerText), + }; + + private ImmutableArray ExtractElementsFromNode(XmlNode node) + { + var elements = ImmutableArray.CreateBuilder(); + foreach (XmlNode child in node.ChildNodes) elements.Add(this.ExtractElement(child)); + return elements.ToImmutable(); + } + + private ReferenceDocumentationElement ConstructReference(XmlNode node) + { + var cref = node.Attributes?["cref"]?.Value; + var symbol = this.GetSymbolFromDocumentationName(cref ?? string.Empty) + // NOTE: The first two characters of the link is the documentation prefix + ?? new PrimitiveTypeSymbol(cref?[2..] ?? string.Empty, false); + return new ReferenceDocumentationElement(symbol, string.IsNullOrEmpty(node.InnerText) ? null : node.InnerText); + } + + private Symbol? GetSymbolFromDocumentationName(string documentationName) => + this.Assembly.Compilation.MetadataAssemblies.Values + .Select(x => x.RootNamespace.LookupByPrefixedDocumentationName(documentationName)) + .OfType() + .FirstOrDefault(); + + private ParameterSymbol? GetParameter(string paramName) => + (this.containingSymbol as FunctionSymbol)?.Parameters.FirstOrDefault(x => x.Name == paramName); + + private TypeParameterSymbol? GetTypeParameter(string paramName) => + (this.containingSymbol as FunctionSymbol)?.GenericParameters.FirstOrDefault(x => x.Name == paramName); +} diff --git a/src/Draco.Compiler/Internal/Documentation/SymbolDocumentation.cs b/src/Draco.Compiler/Internal/Documentation/SymbolDocumentation.cs new file mode 100644 index 000000000..89b619226 --- /dev/null +++ b/src/Draco.Compiler/Internal/Documentation/SymbolDocumentation.cs @@ -0,0 +1,126 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text; +using System.Xml.Linq; + +namespace Draco.Compiler.Internal.Documentation; + +/// +/// Represents documentation for a . +/// +/// The s this documentation contains. +internal record class SymbolDocumentation +{ + /// + /// Empty documentation; + /// + public static SymbolDocumentation Empty = new SymbolDocumentation(ImmutableArray.Empty); + + /// + /// The summary documentation section. + /// + public DocumentationSection? Summary => this.unorderedSections.FirstOrDefault(x => x.Name?.ToLower() == "summary"); + + /// + /// The sections ordered conventionally. + /// + public ImmutableArray Sections => InterlockedUtils.InitializeDefault(ref this.sections, this.BuildOrderedSections); + private ImmutableArray sections; + + private readonly ImmutableArray unorderedSections; + + public SymbolDocumentation(ImmutableArray sections) + { + this.unorderedSections = sections; + } + + /// + /// Creates a markdown representation of this documentation. + /// + /// The documentation in markdown format. + public virtual string ToMarkdown() + { + var builder = new StringBuilder(); + for (var i = 0; i < this.Sections.Length; i++) + { + var section = this.Sections[i]; + builder.Append(section.Kind switch + { + SectionKind.Summary => string.Join(string.Empty, section.Elements.Select(x => x.ToMarkdown())), + + SectionKind.Parameters or SectionKind.TypeParameters => + $""" + # {section.Name} + {string.Join(Environment.NewLine, section.Elements.Select(x => x.ToMarkdown()))} + """, + + SectionKind.Code => section.Elements[0].ToMarkdown(), + + _ => $""" + # {section.Name} + {string.Join(string.Empty, section.Elements.Select(x => x.ToMarkdown()))} + """ + }); + + // Newline after each section except the last one + if (i != this.Sections.Length - 1) builder.Append(Environment.NewLine); + } + return builder.ToString(); + } + + + /// + /// Creates an XML representation of this documentation. + /// + /// The documentation in XML format, encapsulated by a documentation tag. + public virtual XElement ToXml() + { + var sections = new List(); + foreach (var section in this.Sections) + { + switch (section.Kind) + { + case SectionKind.Summary: + sections.Add(new XElement("summary", section.Elements.Select(x => x.ToXml()))); + break; + case SectionKind.Parameters: + case SectionKind.TypeParameters: + sections.AddRange(section.Elements.Select(x => x.ToXml())); + break; + case SectionKind.Code: + sections.Add(section.Elements[0].ToXml()); + break; + default: + // Note: The "Unknown" is for soft failing as string.Empty would throw + sections.Add(new XElement(section.Name, section.Elements.Select(x => x.ToXml()))); + break; + } + } + return new XElement("documentation", sections); + } + + private ImmutableArray BuildOrderedSections() => + this.unorderedSections.OrderBy(x => (int)x.Kind).ToImmutableArray(); +} + +/// +/// Temporary structure for storing markdown documentation. +/// +/// The markdown documentation. +internal sealed record class MarkdownSymbolDocumentation(string Markdown) : SymbolDocumentation(ImmutableArray.Empty) +{ + public override string ToMarkdown() => this.Markdown; + + public override XElement ToXml() => throw new NotSupportedException(); +} + +// TODO: Re-add this once we have proper markdown extractor +#if false +internal sealed record class FunctionDocumentation(ImmutableArray Sections) : SymbolDocumentation(Sections) +{ + public DocumentationSection? Return => this.Sections.FirstOrDefault(x => x.Name.ToLower() == "return"); + public ParametersDocumentationSection? Parameters => this.Sections.OfType().FirstOrDefault(); +} +#endif diff --git a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataAssemblySymbol.cs b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataAssemblySymbol.cs index 705d1adde..b606dde19 100644 --- a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataAssemblySymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataAssemblySymbol.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Reflection; using System.Reflection.Metadata; +using System.Xml; using Draco.Compiler.Api; namespace Draco.Compiler.Internal.Symbols.Metadata; @@ -42,6 +43,11 @@ internal class MetadataAssemblySymbol : ModuleSymbol, IMetadataSymbol public MetadataReader MetadataReader { get; } + /// + /// XmlDocument containing documentation for this assembly. + /// + public XmlDocument? AssemblyDocumentation { get; } + /// /// The compilation this assembly belongs to. /// @@ -52,12 +58,14 @@ internal class MetadataAssemblySymbol : ModuleSymbol, IMetadataSymbol public MetadataAssemblySymbol( Compilation compilation, - MetadataReader metadataReader) + MetadataReader metadataReader, + XmlDocument? documentation) { this.Compilation = compilation; this.MetadataReader = metadataReader; this.moduleDefinition = metadataReader.GetModuleDefinition(); this.assemblyDefinition = metadataReader.GetAssemblyDefinition(); + this.AssemblyDocumentation = documentation; } private MetadataNamespaceSymbol BuildRootNamespace() diff --git a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataFieldSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataFieldSymbol.cs index 4c6e1aaca..41e404483 100644 --- a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataFieldSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataFieldSymbol.cs @@ -1,6 +1,8 @@ using System.Linq; using System.Reflection; using System.Reflection.Metadata; +using Draco.Compiler.Internal.Documentation; +using Draco.Compiler.Internal.Documentation.Extractors; namespace Draco.Compiler.Internal.Symbols.Metadata; @@ -35,6 +37,12 @@ public override Api.Semantics.Visibility Visibility } } + public override SymbolDocumentation Documentation => InterlockedUtils.InitializeNull(ref this.documentation, this.BuildDocumentation); + private SymbolDocumentation? documentation; + + internal override string RawDocumentation => InterlockedUtils.InitializeNull(ref this.rawDocumentation, this.BuildRawDocumentation); + private string? rawDocumentation; + public override Symbol? ContainingSymbol { get; } /// @@ -84,4 +92,10 @@ private TypeSymbol BuildType() var constant = this.MetadataReader.GetConstant(constantHandle); return MetadataSymbol.DecodeConstant(constant, this.MetadataReader); } + + private SymbolDocumentation BuildDocumentation() => + XmlDocumentationExtractor.Extract(this); + + private string BuildRawDocumentation() => + MetadataSymbol.GetDocumentation(this); } diff --git a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataMethodSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataMethodSymbol.cs index 81ed250ec..0cdf860e9 100644 --- a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataMethodSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataMethodSymbol.cs @@ -4,6 +4,8 @@ using System.Reflection; using System.Reflection.Metadata; using System.Threading; +using Draco.Compiler.Internal.Documentation; +using Draco.Compiler.Internal.Documentation.Extractors; namespace Draco.Compiler.Internal.Symbols.Metadata; @@ -87,6 +89,12 @@ public override FunctionSymbol? Override private volatile bool overrideNeedsBuild = true; private readonly object overrideBuildLock = new(); + public override SymbolDocumentation Documentation => InterlockedUtils.InitializeNull(ref this.documentation, this.BuildDocumentation); + private SymbolDocumentation? documentation; + + internal override string RawDocumentation => InterlockedUtils.InitializeNull(ref this.rawDocumentation, this.BuildRawDocumentation); + private string? rawDocumentation; + public override Symbol ContainingSymbol { get; } // IMPORTANT: Choice of flag field because of write order @@ -231,4 +239,10 @@ private static bool SignaturesMatch(FunctionSymbol function, MethodSignature + XmlDocumentationExtractor.Extract(this); + + private string BuildRawDocumentation() => + MetadataSymbol.GetDocumentation(this); } diff --git a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataNamespaceSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataNamespaceSymbol.cs index b22a350df..9a19e1998 100644 --- a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataNamespaceSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataNamespaceSymbol.cs @@ -65,4 +65,28 @@ private ImmutableArray BuildMembers() // Done return result.ToImmutable(); } + + /// + /// Looks up symbol by its prefixed documentation name. + /// + /// The prefixed documentation name to lookup by. + /// The looked up symbol, or null, if such symbol doesn't exist under this module symbol. + public Symbol? LookupByPrefixedDocumentationName(string prefixedDocumentationName) + { + // Note: we cut off the first two chars, because the first two chars are always the prefix annotating what kind of symbol this is + var parts = prefixedDocumentationName[2..].Split('.'); + if (parts.Length == 0) return this; + + var current = this as Symbol; + for (var i = 0; i < parts.Length - 1; ++i) + { + var part = parts[i]; + current = current.Members + .Where(m => m.MetadataName == part && m is ModuleSymbol or TypeSymbol) + .SingleOrDefault(); + if (current is null) return null; + } + + return current.Members.SingleOrDefault(m => MetadataSymbol.GetPrefixedDocumentationName(m) == prefixedDocumentationName); + } } diff --git a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataPropertySymbol.cs b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataPropertySymbol.cs index 90b5d2848..58f518a55 100644 --- a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataPropertySymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataPropertySymbol.cs @@ -1,6 +1,8 @@ using System; using System.Linq; using System.Reflection.Metadata; +using Draco.Compiler.Internal.Documentation; +using Draco.Compiler.Internal.Documentation.Extractors; namespace Draco.Compiler.Internal.Symbols.Metadata; @@ -43,6 +45,12 @@ public override PropertySymbol? Override private volatile bool overrideNeedsBuild = true; private readonly object overrideBuildLock = new(); + public override SymbolDocumentation Documentation => InterlockedUtils.InitializeNull(ref this.documentation, this.BuildDocumentation); + private SymbolDocumentation? documentation; + + internal override string RawDocumentation => InterlockedUtils.InitializeNull(ref this.rawDocumentation, this.BuildRawDocumentation); + private string? rawDocumentation; + public override Symbol ContainingSymbol { get; } /// @@ -99,4 +107,10 @@ private void BuildOverride() if (accessor.Override is not null) return (accessor.Override as IPropertyAccessorSymbol)?.Property; return null; } + + private SymbolDocumentation BuildDocumentation() => + XmlDocumentationExtractor.Extract(this); + + private string BuildRawDocumentation() => + MetadataSymbol.GetDocumentation(this); } diff --git a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataStaticClassSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataStaticClassSymbol.cs index 8160ba72c..ffb73d131 100644 --- a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataStaticClassSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataStaticClassSymbol.cs @@ -3,6 +3,8 @@ using System.Linq; using System.Reflection; using System.Reflection.Metadata; +using Draco.Compiler.Internal.Documentation; +using Draco.Compiler.Internal.Documentation.Extractors; namespace Draco.Compiler.Internal.Symbols.Metadata; @@ -20,6 +22,12 @@ internal sealed class MetadataStaticClassSymbol : ModuleSymbol, IMetadataSymbol, public override Api.Semantics.Visibility Visibility => this.typeDefinition.Attributes.HasFlag(TypeAttributes.Public) ? Api.Semantics.Visibility.Public : Api.Semantics.Visibility.Internal; + public override SymbolDocumentation Documentation => InterlockedUtils.InitializeNull(ref this.documentation, this.BuildDocumentation); + private SymbolDocumentation? documentation; + + internal override string RawDocumentation => InterlockedUtils.InitializeNull(ref this.rawDocumentation, this.BuildRawDocumentation); + private string? rawDocumentation; + public override Symbol ContainingSymbol { get; } // NOTE: thread-safety does not matter, same instance @@ -102,4 +110,10 @@ private ImmutableArray BuildMembers() // Done return result.ToImmutable(); } + + private SymbolDocumentation BuildDocumentation() => + XmlDocumentationExtractor.Extract(this); + + private string BuildRawDocumentation() => + MetadataSymbol.GetDocumentation(this); } diff --git a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataSymbol.cs index 8cf1b6360..0fcc0b05e 100644 --- a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataSymbol.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Reflection.Metadata; using Draco.Compiler.Api; @@ -70,7 +71,7 @@ public static IEnumerable ToSymbol( var memberType = reader.GetTypeReference((TypeReferenceHandle)member.Parent); if (reader.GetString(memberType.Name) == "DefaultMemberAttribute") return attribute.DecodeValue(typeProvider).FixedArguments[0].Value?.ToString(); break; - default: throw new System.InvalidOperationException(); + default: throw new InvalidOperationException(); }; } return null; @@ -108,4 +109,74 @@ public static IEnumerable ToSymbol( private static FunctionSymbol SynthetizeConstructor( MetadataTypeSymbol type, MethodDefinition ctorMethod) => new SynthetizedMetadataConstructorSymbol(type, ctorMethod); + + /// + /// Gets the documentation XML as text for the given . + /// + /// The to get documentation for. + /// The documentation, or empty string, if no documentation was found. + public static string GetDocumentation(Symbol symbol) + { + var assembly = symbol.AncestorChain.OfType().FirstOrDefault(); + if (assembly is null) return string.Empty; + var documentationName = GetPrefixedDocumentationName(symbol); + var root = assembly.AssemblyDocumentation?.DocumentElement; + var xml = root?.SelectSingleNode($"//member[@name='{documentationName}']")?.InnerXml ?? string.Empty; + return string.Join(Environment.NewLine, xml.ReplaceLineEndings("\n").Split('\n').Select(x => x.TrimStart())); + } + + /// + /// Gets the full name of a used to retrieve documentation from metadata. + /// + /// The symbol to get documentation name of. + /// The documentation name, or empty string, if is null. + public static string GetDocumentationName(Symbol? symbol) => symbol switch + { + FunctionSymbol function => GetFunctionDocumentationName(function), + TypeParameterSymbol typeParam => GetTypeParameterDocumentationName(typeParam), + null => string.Empty, + _ => symbol.MetadataFullName, + }; + + /// + /// The documentation name of with prepended documentation prefix, documentation prefix specifies the type of symbol the documentation name represents. + /// For example has the prefix "T:". + /// + /// The symbol to get prefixed documentation name of. + /// The prefixed documentation name, or empty string, if is null. + public static string GetPrefixedDocumentationName(Symbol? symbol) => $"{GetDocumentationPrefix(symbol)}{GetDocumentationName(symbol)}"; + + private static string GetDocumentationPrefix(Symbol? symbol) => symbol switch + { + TypeSymbol => "T:", + ModuleSymbol => "T:", + FunctionSymbol => "M:", + PropertySymbol => "P:", + FieldSymbol => "F:", + _ => string.Empty, + }; + + private static string GetFunctionDocumentationName(FunctionSymbol function) + { + var parametersJoined = function.Parameters.Length == 0 + ? string.Empty + : $"({string.Join(",", function.Parameters.Select(x => GetDocumentationName(x.Type)))})"; + + var generics = function.GenericParameters.Length == 0 + ? string.Empty + : $"``{function.GenericParameters.Length}"; + return $"{function.MetadataFullName}{generics}{parametersJoined}"; + } + + private static string GetTypeParameterDocumentationName(TypeParameterSymbol typeParameter) + { + var index = typeParameter.ContainingSymbol?.GenericParameters.IndexOf(typeParameter); + if (index is null || index.Value == -1) return typeParameter.MetadataFullName; + return typeParameter.ContainingSymbol switch + { + TypeSymbol => $"`{index.Value}", + FunctionSymbol => $"``{index.Value}", + _ => typeParameter.MetadataFullName, + }; + } } diff --git a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataTypeSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataTypeSymbol.cs index fef1c243c..a14ad8f2b 100644 --- a/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataTypeSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Metadata/MetadataTypeSymbol.cs @@ -4,6 +4,8 @@ using System.Linq; using System.Reflection; using System.Reflection.Metadata; +using Draco.Compiler.Internal.Documentation; +using Draco.Compiler.Internal.Documentation.Extractors; namespace Draco.Compiler.Internal.Symbols.Metadata; @@ -27,6 +29,12 @@ internal sealed class MetadataTypeSymbol : TypeSymbol, IMetadataSymbol, IMetadat InterlockedUtils.InitializeDefault(ref this.genericParameters, this.BuildGenericParameters); private ImmutableArray genericParameters; + public override SymbolDocumentation Documentation => InterlockedUtils.InitializeNull(ref this.documentation, this.BuildDocumentation); + private SymbolDocumentation? documentation; + + internal override string RawDocumentation => InterlockedUtils.InitializeNull(ref this.rawDocumentation, this.BuildRawDocumentation); + private string? rawDocumentation; + public override Symbol ContainingSymbol { get; } public override bool IsValueType => this.BaseTypes.Contains( @@ -169,4 +177,10 @@ private ImmutableArray BuildMembers() // Done return result.ToImmutable(); } + + private SymbolDocumentation BuildDocumentation() => + XmlDocumentationExtractor.Extract(this); + + private string BuildRawDocumentation() => + MetadataSymbol.GetDocumentation(this); } diff --git a/src/Draco.Compiler/Internal/Symbols/Source/SourceFunctionSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Source/SourceFunctionSymbol.cs index 6007e33c8..4a934edac 100644 --- a/src/Draco.Compiler/Internal/Symbols/Source/SourceFunctionSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Source/SourceFunctionSymbol.cs @@ -6,6 +6,8 @@ using Draco.Compiler.Internal.BoundTree; using Draco.Compiler.Internal.Declarations; using Draco.Compiler.Internal.Diagnostics; +using Draco.Compiler.Internal.Documentation; +using Draco.Compiler.Internal.Documentation.Extractors; using Draco.Compiler.Internal.FlowAnalysis; using Draco.Compiler.Internal.Symbols.Synthetized; @@ -34,7 +36,10 @@ internal sealed class SourceFunctionSymbol : FunctionSymbol, ISourceSymbol public BoundStatement Body => this.BindBodyIfNeeded(this.DeclaringCompilation!); private BoundStatement? body; - public override string Documentation => this.DeclaringSyntax.Documentation; + public override SymbolDocumentation Documentation => InterlockedUtils.InitializeNull(ref this.documentation, this.BuildDocumentation); + private SymbolDocumentation? documentation; + + internal override string RawDocumentation => this.DeclaringSyntax.Documentation; public SourceFunctionSymbol(Symbol? containingSymbol, FunctionDeclarationSyntax syntax) { @@ -204,4 +209,7 @@ private static bool HasSameParameterTypes(FunctionSymbol f1, FunctionSymbol f2) return true; } + + private SymbolDocumentation BuildDocumentation() => + MarkdownDocumentationExtractor.Extract(this); } diff --git a/src/Draco.Compiler/Internal/Symbols/Source/SourceGlobalSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Source/SourceGlobalSymbol.cs index 87ecd28cb..428c64020 100644 --- a/src/Draco.Compiler/Internal/Symbols/Source/SourceGlobalSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Source/SourceGlobalSymbol.cs @@ -3,6 +3,8 @@ using Draco.Compiler.Internal.Binding; using Draco.Compiler.Internal.BoundTree; using Draco.Compiler.Internal.Declarations; +using Draco.Compiler.Internal.Documentation; +using Draco.Compiler.Internal.Documentation.Extractors; using Draco.Compiler.Internal.FlowAnalysis; namespace Draco.Compiler.Internal.Symbols.Source; @@ -19,7 +21,10 @@ internal sealed class SourceGlobalSymbol : GlobalSymbol, ISourceSymbol public BoundExpression? Value => this.BindTypeAndValueIfNeeded(this.DeclaringCompilation!).Value; - public override string Documentation => this.DeclaringSyntax.Documentation; + public override SymbolDocumentation Documentation => InterlockedUtils.InitializeNull(ref this.documentation, this.BuildDocumentation); + private SymbolDocumentation? documentation; + + internal override string RawDocumentation => this.DeclaringSyntax.Documentation; // IMPORTANT: flag is type, needs to be written last // NOTE: We check the TYPE here, as value is nullable @@ -69,4 +74,7 @@ public void Bind(IBinderProvider binderProvider) var binder = binderProvider.GetBinder(this.DeclaringSyntax); return binder.BindGlobal(this, binderProvider.DiagnosticBag); } + + private SymbolDocumentation BuildDocumentation() => + MarkdownDocumentationExtractor.Extract(this); } diff --git a/src/Draco.Compiler/Internal/Symbols/Source/SourceLocalSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Source/SourceLocalSymbol.cs index 41a71edbc..55f1bebea 100644 --- a/src/Draco.Compiler/Internal/Symbols/Source/SourceLocalSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Source/SourceLocalSymbol.cs @@ -1,5 +1,7 @@ using Draco.Compiler.Api.Syntax; using Draco.Compiler.Internal.Binding; +using Draco.Compiler.Internal.Documentation; +using Draco.Compiler.Internal.Documentation.Extractors; namespace Draco.Compiler.Internal.Symbols.Source; @@ -17,7 +19,10 @@ internal sealed class SourceLocalSymbol : LocalSymbol, ISourceSymbol public override bool IsMutable => this.untypedSymbol.IsMutable; - public override string Documentation => this.DeclaringSyntax.Documentation; + public override SymbolDocumentation Documentation => InterlockedUtils.InitializeNull(ref this.documentation, this.BuildDocumentation); + private SymbolDocumentation? documentation; + + internal override string RawDocumentation => this.DeclaringSyntax.Documentation; private readonly UntypedLocalSymbol untypedSymbol; @@ -28,4 +33,7 @@ public SourceLocalSymbol(UntypedLocalSymbol untypedSymbol, TypeSymbol type) } public void Bind(IBinderProvider binderProvider) { } + + private SymbolDocumentation BuildDocumentation() => + MarkdownDocumentationExtractor.Extract(this); } diff --git a/src/Draco.Compiler/Internal/Symbols/Source/SourceModuleSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Source/SourceModuleSymbol.cs index e1d2e34bd..c5dcfa9cd 100644 --- a/src/Draco.Compiler/Internal/Symbols/Source/SourceModuleSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Source/SourceModuleSymbol.cs @@ -8,6 +8,8 @@ using Draco.Compiler.Api.Syntax; using Draco.Compiler.Internal.Binding; using Draco.Compiler.Internal.Declarations; +using Draco.Compiler.Internal.Documentation; +using Draco.Compiler.Internal.Documentation.Extractors; namespace Draco.Compiler.Internal.Symbols.Source; @@ -24,13 +26,19 @@ internal sealed class SourceModuleSymbol : ModuleSymbol, ISourceSymbol public override Symbol? ContainingSymbol { get; } public override string Name => this.declaration.Name; - public override SyntaxNode? DeclaringSyntax => null; + public override SymbolDocumentation Documentation => InterlockedUtils.InitializeNull(ref this.documentation, this.BuildDocumentation); + private SymbolDocumentation? documentation; /// /// The syntaxes contributing to this module. /// public IEnumerable DeclaringSyntaxes => this.declaration.DeclaringSyntaxes; + internal override string RawDocumentation => this.DeclaringSyntaxes + .Select(syntax => syntax.Documentation) + .Where(doc => !string.IsNullOrEmpty(doc)) + .FirstOrDefault() ?? string.Empty; + private readonly Declaration declaration; private SourceModuleSymbol( @@ -43,14 +51,6 @@ private SourceModuleSymbol( this.declaration = declaration; } - public SourceModuleSymbol( - Compilation compilation, - Symbol? containingSymbol, - SingleModuleDeclaration declaration) - : this(compilation, containingSymbol, declaration as Declaration) - { - } - public SourceModuleSymbol( Compilation compilation, Symbol? containingSymbol, @@ -105,4 +105,7 @@ private ImmutableArray BindMembers(IBinderProvider binderProvider) private FunctionSymbol BuildFunction(FunctionDeclaration declaration) => new SourceFunctionSymbol(this, declaration); private GlobalSymbol BuildGlobal(GlobalDeclaration declaration) => new SourceGlobalSymbol(this, declaration); private ModuleSymbol BuildModule(MergedModuleDeclaration declaration) => new SourceModuleSymbol(this.DeclaringCompilation, this, declaration); + + private SymbolDocumentation BuildDocumentation() => + MarkdownDocumentationExtractor.Extract(this); } diff --git a/src/Draco.Compiler/Internal/Symbols/Symbol.cs b/src/Draco.Compiler/Internal/Symbols/Symbol.cs index 7c3ac51c3..c17a6ab63 100644 --- a/src/Draco.Compiler/Internal/Symbols/Symbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Symbol.cs @@ -3,7 +3,9 @@ using System.Linq; using Draco.Compiler.Api; using Draco.Compiler.Api.Syntax; +using Draco.Compiler.Internal.Documentation; using Draco.Compiler.Internal.Symbols.Generic; +using Draco.Compiler.Internal.Symbols.Metadata; using Draco.Compiler.Internal.Utilities; namespace Draco.Compiler.Internal.Symbols; @@ -89,6 +91,23 @@ public virtual string FullName } } + /// + /// The fully qualified metadata name of this symbol. + /// + public virtual string MetadataFullName + { + get + { + var parentFullName = this.ContainingSymbol is not MetadataAssemblySymbol + ? this.ContainingSymbol?.MetadataFullName + : null; + + return string.IsNullOrWhiteSpace(parentFullName) + ? this.MetadataName + : $"{parentFullName}.{this.MetadataName}"; + } + } + /// /// All the members within this symbol. /// @@ -105,9 +124,14 @@ public virtual string FullName public virtual IEnumerable InstanceMembers => this.Members.Where(x => x is IMemberSymbol mem && !mem.IsStatic); /// - /// Documentation attached to this symbol. + /// The structured documentation attached to this symbol. + /// + public virtual SymbolDocumentation Documentation => SymbolDocumentation.Empty; + + /// + /// The documentation of symbol as raw xml or markdown; /// - public virtual string Documentation => string.Empty; + internal virtual string RawDocumentation => string.Empty; /// /// The visibility of this symbol. diff --git a/src/Draco.Compiler/Internal/Symbols/Synthetized/MetadataBackedPrimitiveTypeSymbol.cs b/src/Draco.Compiler/Internal/Symbols/Synthetized/MetadataBackedPrimitiveTypeSymbol.cs index 34ea6a3ad..4f0e6012b 100644 --- a/src/Draco.Compiler/Internal/Symbols/Synthetized/MetadataBackedPrimitiveTypeSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/Synthetized/MetadataBackedPrimitiveTypeSymbol.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Collections.Immutable; +using Draco.Compiler.Internal.Documentation; namespace Draco.Compiler.Internal.Symbols.Synthetized; @@ -16,9 +17,11 @@ internal sealed class MetadataBackedPrimitiveTypeSymbol : PrimitiveTypeSymbol /// public TypeSymbol MetadataType { get; } + public override string MetadataName => this.MetadataType.MetadataName; + public override string MetadataFullName => this.MetadataType.MetadataFullName; public override ImmutableArray ImmediateBaseTypes => this.MetadataType.ImmediateBaseTypes; public override IEnumerable DefinedMembers => this.MetadataType.DefinedMembers; - public override string Documentation => this.MetadataType.Documentation; + public override SymbolDocumentation Documentation => this.MetadataType.Documentation; public MetadataBackedPrimitiveTypeSymbol(string name, bool isValueType, TypeSymbol metadataType) : base(name, isValueType) diff --git a/src/Draco.Compiler/Internal/Symbols/TypeParameterSymbol.cs b/src/Draco.Compiler/Internal/Symbols/TypeParameterSymbol.cs index 709fcae40..3ef0a26ec 100644 --- a/src/Draco.Compiler/Internal/Symbols/TypeParameterSymbol.cs +++ b/src/Draco.Compiler/Internal/Symbols/TypeParameterSymbol.cs @@ -9,7 +9,7 @@ namespace Draco.Compiler.Internal.Symbols; internal abstract class TypeParameterSymbol : TypeSymbol { public override TypeSymbol GenericInstantiate(Symbol? containingSymbol, ImmutableArray arguments) => - (TypeSymbol)base.GenericInstantiate(containingSymbol, arguments); + base.GenericInstantiate(containingSymbol, arguments); public override TypeSymbol GenericInstantiate(Symbol? containingSymbol, GenericContext context) => context.TryGetValue(this, out var type) ? type : this; diff --git a/src/Draco.Compiler/Internal/Symbols/TypeVariable.cs b/src/Draco.Compiler/Internal/Symbols/TypeVariable.cs index d4e576b68..546a9a072 100644 --- a/src/Draco.Compiler/Internal/Symbols/TypeVariable.cs +++ b/src/Draco.Compiler/Internal/Symbols/TypeVariable.cs @@ -24,7 +24,6 @@ public override bool IsGroundType public override bool IsError => throw new NotSupportedException(); public override Symbol? ContainingSymbol => throw new NotSupportedException(); public override IEnumerable DefinedMembers => throw new NotSupportedException(); - public override string Documentation => throw new NotSupportedException(); public override TypeSymbol Substitution => this.solver.Unwrap(this);