Skip to content

VB SymbolDisplay.ToDisplayString doesn't use Optional keyword when IncludeDefaultValue and not IncludeOptionalBrackets #77458

@Nukepayload2

Description

@Nukepayload2

I use Microsoft.CodeAnalysis.VisualBasic.SymbolDisplay.ToDisplayString to generate VB declarations from dll files and xml files in my API docs. I found that the Optional keyword is missing when generating optional parameters in method declarations when the SymbolDisplayParameterOptions.IncludeDefaultValue flag is set and SymbolDisplayParameterOptions.IncludeOptionalBrackets is not set.
I need to display VB declarations with the exactly the same format as Microsoft Learn. To work around this bug, I have to use ToDisplayParts and adjust the return value, which is very inconvenient.

Version Used:
4.13.0

Steps to Reproduce:

Create a VB console project

VbSymbolDisplayCsOptional.vbproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <RootNamespace>VbSymbolDisplayCsOptional</RootNamespace>
    <TargetFramework>net9.0</TargetFramework>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.13.0" />
    <PackageReference Include="Microsoft.CodeAnalysis.VisualBasic" Version="4.13.0" />
  </ItemGroup>

</Project>

Edit Program.vb

Program.vb

Imports System.IO
Imports Microsoft.CodeAnalysis
Imports CS = Microsoft.CodeAnalysis.CSharp
Imports VB = Microsoft.CodeAnalysis.VisualBasic

Module Program

    Sub Main()
        ' Create C# code with optional parameter
        Dim csharpCode As String = "
using System;

public class Example
{
    public void MethodWithOptional(string required, string str1 = ""default"", int number = 42)
    {
        Console.WriteLine($""{required}, {str1}, {number}"");
    }
}"

        ' Compile the C# code
        Dim compilation = CreateCompilationFromCSharpCode(csharpCode, "OptionalParamExample")

        ' Get the symbol for the method with optional parameters
        Dim exampleClass = compilation.GetTypeByMetadataName("Example")
        Dim methodSymbol = exampleClass.GetMembers().OfType(Of IMethodSymbol)().First(Function(m) m.Name = "MethodWithOptional")

        ' Create a VB symbol display format
        Dim format = SymbolDisplayFormat.FullyQualifiedFormat.
            WithMemberOptions(
                SymbolDisplayMemberOptions.IncludeParameters).
            WithParameterOptions(
                SymbolDisplayParameterOptions.IncludeName Or
                SymbolDisplayParameterOptions.IncludeType Or
                SymbolDisplayParameterOptions.IncludeDefaultValue Or
                SymbolDisplayParameterOptions.IncludeModifiers)

        ' Display the symbol using VB SymbolDisplay
        Dim formattedSymbol = VB.SymbolDisplay.ToDisplayString(methodSymbol, format)
        Console.WriteLine("Method signature in VB format:")
        Console.WriteLine(formattedSymbol)

        ' Also display the parameter info separately for clarity
        Console.WriteLine("Parameters:")
        For Each parameter In methodSymbol.Parameters
            Dim paramStr = VB.SymbolDisplay.ToDisplayString(parameter, format)
            Dim hasDefaultValue = parameter.HasExplicitDefaultValue
            Console.WriteLine($"- {paramStr} (Optional: {parameter.IsOptional}, Has Default: {hasDefaultValue})")
        Next
    End Sub

    Public Function CreateCompilationFromCSharpCode(
            code As String,
            name As String,
            ParamArray references As MetadataReference()) As Compilation

        Dim parserOption As New CS.CSharpParseOptions
        Dim syntaxTree = CS.CSharpSyntaxTree.ParseText(code, parserOption)

        Return CS.CSharpCompilation.Create(
            name,
            options:=New CS.CSharpCompilationOptions(
                OutputKind.DynamicallyLinkedLibrary,
                xmlReferenceResolver:=XmlFileResolver.Default),
            syntaxTrees:={syntaxTree},
            references:=GetDefaultMetadataReferences().
            Concat(If(references, Array.Empty(Of MetadataReference))))
    End Function

    Private Function GetDefaultMetadataReferences() As IEnumerable(Of MetadataReference)
        Dim dirPath = Path.GetDirectoryName(GetType(Object).Assembly.Location)

        Return From dll In Directory.EnumerateFiles(dirPath, "*.dll", SearchOption.TopDirectoryOnly)
               Select MetadataReference.CreateFromFile(dll)
    End Function

End Module

Run the project.

Diagnostic Id:

N/A

Expected Behavior:
The program produces the following output:

Method signature in VB format:
MethodWithOptional(required As String, Optional str1 As String = "default", Optional number As Integer = 42)
Parameters:
- required As String (Optional: False, Has Default: False)
- Optional str1 As String = "default" (Optional: True, Has Default: True)
- Optional number As Integer = 42 (Optional: True, Has Default: True)

Actual Behavior:
The program produces the following output:

Method signature in VB format:
MethodWithOptional(required As String, str1 As String = "default", number As Integer = 42)
Parameters:
- required As String (Optional: False, Has Default: False)
- str1 As String = "default" (Optional: True, Has Default: True)
- number As Integer = 42 (Optional: True, Has Default: True)

Metadata

Metadata

Assignees

No one assigned

    Type

    No fields configured for Bug.

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions