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);