File: Lowering\LocalRewriter\LocalRewriter_XmlLiterals.vb
Web Access
Project: src\roslyn\src\Compilers\VisualBasic\Portable\Microsoft.CodeAnalysis.VisualBasic.vbproj (Microsoft.CodeAnalysis.VisualBasic)
' Licensed to the .NET Foundation under one or more agreements.
' The .NET Foundation licenses this file to you under the MIT license.
' See the LICENSE file in the project root for more information.

Imports System.Collections.Immutable
Imports System.Runtime.InteropServices
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols

Namespace Microsoft.CodeAnalysis.VisualBasic

    Partial Friend Class LocalRewriter

        Public Overrides Function VisitXmlComment(node As BoundXmlComment) As BoundNode
            Return Visit(node.ObjectCreation)
        End Function

        Public Overrides Function VisitXmlDocument(node As BoundXmlDocument) As BoundNode
            If Me._inExpressionLambda AndAlso Not node.HasErrors Then
                Return VisitXmlContainerInExpressionLambda(node.RewriterInfo)
            Else
                Return VisitXmlContainer(node.RewriterInfo)
            End If
        End Function

        Public Overrides Function VisitXmlDeclaration(node As BoundXmlDeclaration) As BoundNode
            Return Visit(node.ObjectCreation)
        End Function

        Public Overrides Function VisitXmlProcessingInstruction(node As BoundXmlProcessingInstruction) As BoundNode
            Return Visit(node.ObjectCreation)
        End Function

        Public Overrides Function VisitXmlAttribute(node As BoundXmlAttribute) As BoundNode
            Return Visit(node.ObjectCreation)
        End Function

        Public Overrides Function VisitXmlElement(node As BoundXmlElement) As BoundNode
            Dim rewriterInfo = node.RewriterInfo
            Dim previousImportedNamespaces = Me._xmlImportedNamespaces

            If rewriterInfo.IsRoot Then
                Debug.Assert(Not rewriterInfo.ImportedNamespaces.IsDefault)
                Me._xmlImportedNamespaces = rewriterInfo.ImportedNamespaces
            End If

            Dim result As BoundNode
            If Me._inExpressionLambda AndAlso Not node.HasErrors Then
                result = VisitXmlContainerInExpressionLambda(rewriterInfo)
            Else
                result = VisitXmlContainer(rewriterInfo)
            End If

            If rewriterInfo.IsRoot Then
                Me._xmlImportedNamespaces = previousImportedNamespaces
            End If

            Return result
        End Function

        Public Overrides Function VisitXmlEmbeddedExpression(node As BoundXmlEmbeddedExpression) As BoundNode
            Return Visit(node.Expression)
        End Function

        Public Overrides Function VisitXmlMemberAccess(node As BoundXmlMemberAccess) As BoundNode
            Return Visit(node.MemberAccess)
        End Function

        Public Overrides Function VisitXmlName(node As BoundXmlName) As BoundNode
            Return Visit(node.ObjectCreation)
        End Function

        Public Overrides Function VisitXmlNamespace(node As BoundXmlNamespace) As BoundNode
            Return Visit(node.ObjectCreation)
        End Function

        Public Overrides Function VisitXmlCData(node As BoundXmlCData) As BoundNode
            Return Visit(node.ObjectCreation)
        End Function

        Private Function VisitXmlContainer(rewriterInfo As BoundXmlContainerRewriterInfo) As BoundExpression
            Debug.Assert(Not Me._inExpressionLambda)

            Dim expr = VisitExpressionNode(rewriterInfo.ObjectCreation)

            If rewriterInfo.SideEffects.Length = 0 Then
                Debug.Assert(rewriterInfo.XmlnsAttributesPlaceholder Is Nothing)
                Return expr
            End If

            Dim syntax = expr.Syntax
            Dim type = expr.Type

            Dim locals = ArrayBuilder(Of LocalSymbol).GetInstance()
            Dim sideEffects = ArrayBuilder(Of BoundExpression).GetInstance()

            Dim local = CreateTempLocal(syntax, type, expr, sideEffects)
            locals.Add(local.LocalSymbol)
            AddPlaceholderReplacement(rewriterInfo.Placeholder, local)

            Dim attributes As BoundLocal = Nothing

            If rewriterInfo.XmlnsAttributesPlaceholder IsNot Nothing Then
                attributes = CreateTempLocal(syntax, rewriterInfo.XmlnsAttributesPlaceholder.Type, VisitExpressionNode(rewriterInfo.XmlnsAttributes), sideEffects)
                locals.Add(attributes.LocalSymbol)
                AddPlaceholderReplacement(rewriterInfo.XmlnsAttributesPlaceholder, attributes)
            End If

            If rewriterInfo.PrefixesPlaceholder IsNot Nothing Then
                Dim prefixesArray As BoundExpression = Nothing
                Dim namespacesArray As BoundExpression = Nothing
                CreatePrefixesAndNamespacesArrays(rewriterInfo, syntax, prefixesArray, namespacesArray)

                Dim prefixes = CreateTempLocal(syntax, rewriterInfo.PrefixesPlaceholder.Type, prefixesArray, sideEffects)
                locals.Add(prefixes.LocalSymbol)
                AddPlaceholderReplacement(rewriterInfo.PrefixesPlaceholder, prefixes)

                Dim namespaces = CreateTempLocal(syntax, rewriterInfo.NamespacesPlaceholder.Type, namespacesArray, sideEffects)
                locals.Add(namespaces.LocalSymbol)
                AddPlaceholderReplacement(rewriterInfo.NamespacesPlaceholder, namespaces)
            End If

            VisitList(rewriterInfo.SideEffects, sideEffects)

            If rewriterInfo.PrefixesPlaceholder IsNot Nothing Then
                RemovePlaceholderReplacement(rewriterInfo.PrefixesPlaceholder)
                RemovePlaceholderReplacement(rewriterInfo.NamespacesPlaceholder)
            End If

            If rewriterInfo.XmlnsAttributesPlaceholder IsNot Nothing Then
                RemovePlaceholderReplacement(rewriterInfo.XmlnsAttributesPlaceholder)
            End If

            RemovePlaceholderReplacement(rewriterInfo.Placeholder)

            Return New BoundSequence(syntax,
                                     locals:=locals.ToImmutableAndFree(),
                                     sideEffects:=sideEffects.ToImmutableAndFree(),
                                     valueOpt:=local,
                                     type:=type)
        End Function

        ''' <summary>
        ''' Create a temp local of the given type and initial value.
        ''' The resulting local is treated as an rvalue, and the
        ''' initialization assignment is added to 'sideEffects'.
        ''' </summary>
        Private Function CreateTempLocal(syntax As SyntaxNode, type As TypeSymbol, expr As BoundExpression, sideEffects As ArrayBuilder(Of BoundExpression)) As BoundLocal
            Dim local = New BoundLocal(syntax, New SynthesizedLocal(Me._currentMethodOrLambda, type, SynthesizedLocalKind.LoweringTemp), type)
            sideEffects.Add(New BoundAssignmentOperator(syntax, local, expr, suppressObjectClone:=True, type:=type))
            Return local.MakeRValue()
        End Function

        Private Function VisitXmlContainerInExpressionLambda(rewriterInfo As BoundXmlContainerRewriterInfo) As BoundExpression
            Debug.Assert(Me._inExpressionLambda)

            ' NOTE: When we convert Xml container expression in context of expression lambda we rewrite it into 
            '       one of the following: New XElement(XName, Object), New XElement(XName, Object()) or
            '       New XDocument(XName, Object()) where the second parameter, an object or object array, contains all 
            '       expressions from rewriterInfo.SideEffects (which are supposed to be in a form of <l>.Add(<e>));
            '
            '       In case there are any extra locals introduced by rewriterInfo.XmlnsAttributes, rewriterInfo.Prefixes
            '       or rewriterInfo.Namespaces, those need to be put into preamble for the method so that they are 
            '       initialized outside expression tree lambda and captured properly

            Dim origSideEffects As ImmutableArray(Of BoundExpression) = rewriterInfo.SideEffects

            Debug.Assert(rewriterInfo.ObjectCreation.Kind = BoundKind.ObjectCreationExpression)
            Dim objCreation = DirectCast(rewriterInfo.ObjectCreation, BoundObjectCreationExpression)

            ' if there are no side effects just rewrite object creation expression
            If origSideEffects.Length = 0 Then
                Debug.Assert(rewriterInfo.XmlnsAttributesPlaceholder Is Nothing)
                Return VisitExpressionNode(objCreation)
            End If

            Dim origArgument As BoundExpression = objCreation.Arguments(0)

            Debug.Assert(objCreation.InitializerOpt Is Nothing)
            Debug.Assert(objCreation.ConstructorOpt IsNot Nothing)

            ' NOTE: if we are here, original bound node does not have errors, thus we 
            '       can assume the symbols used in this node are OK
            Dim constructor As MethodSymbol = Nothing
            If objCreation.Arguments.Length = 1 Then
                ' This is a branch where XElement::.ctor(XName) lands, we need to get XElement::.ctor(XName, Object()) 
                Debug.Assert(TypeSymbol.Equals(objCreation.ConstructorOpt.ContainingType, Me.Compilation.GetWellKnownType(WellKnownType.System_Xml_Linq_XElement), TypeCompareKind.ConsiderEverything))

                constructor = DirectCast(Me.Compilation.GetWellKnownTypeMember(If(origSideEffects.Length = 1,
                                                                                  WellKnownMember.System_Xml_Linq_XElement__ctor,
                                                                                  WellKnownMember.System_Xml_Linq_XElement__ctor2)), MethodSymbol)

                If ReportMissingOrBadRuntimeHelper(objCreation, WellKnownMember.System_Xml_Linq_XElement__ctor2, constructor) Then
                    Return VisitExpressionNode(objCreation)
                End If

            Else
                ' This is a branch where XDocument::.ctor(XDeclaration, Object()) lands
                Debug.Assert(objCreation.Arguments.Length = 2)
                constructor = objCreation.ConstructorOpt
                Debug.Assert(TypeSymbol.Equals(constructor.ContainingType, Me.Compilation.GetWellKnownType(WellKnownType.System_Xml_Linq_XDocument), TypeCompareKind.ConsiderEverything))
            End If

            Dim syntax = objCreation.Syntax
            Dim type = objCreation.Type

            ' NOTE: rewriterInfo.Placeholder should never be used in rewriting below, because it is supposed
            '       to be on the receiver side of side-effect calls and we never rewrite those
            'AddPlaceholderReplacement(rewriterInfo.Placeholder, local)

            Dim attributes As BoundLocal = Nothing

            If rewriterInfo.XmlnsAttributesPlaceholder IsNot Nothing Then
                attributes = CreateTempLocalInExpressionLambda(syntax, rewriterInfo.XmlnsAttributesPlaceholder.Type, VisitExpressionNode(rewriterInfo.XmlnsAttributes))
                AddPlaceholderReplacement(rewriterInfo.XmlnsAttributesPlaceholder, attributes)
            End If

            If rewriterInfo.PrefixesPlaceholder IsNot Nothing Then
                Dim prefixesArray As BoundExpression = Nothing
                Dim namespacesArray As BoundExpression = Nothing
                CreatePrefixesAndNamespacesArrays(rewriterInfo, syntax, prefixesArray, namespacesArray)

                Dim prefixes = CreateTempLocalInExpressionLambda(syntax, rewriterInfo.PrefixesPlaceholder.Type, prefixesArray)
                AddPlaceholderReplacement(rewriterInfo.PrefixesPlaceholder, prefixes)

                Dim namespaces = CreateTempLocalInExpressionLambda(syntax, rewriterInfo.NamespacesPlaceholder.Type, namespacesArray)
                AddPlaceholderReplacement(rewriterInfo.NamespacesPlaceholder, namespaces)
            End If

            Dim rewrittenCallArguments(origSideEffects.Length - 1) As BoundExpression
            For i = 0 To origSideEffects.Length - 1
                Debug.Assert(origSideEffects(i).Kind = BoundKind.Call)
                Dim [call] = DirectCast(origSideEffects(i), BoundCall)
                Debug.Assert([call].Arguments.Length = 1)
                rewrittenCallArguments(i) = VisitExpressionNode([call].Arguments(0))
            Next

            If rewriterInfo.PrefixesPlaceholder IsNot Nothing Then
                RemovePlaceholderReplacement(rewriterInfo.PrefixesPlaceholder)
                RemovePlaceholderReplacement(rewriterInfo.NamespacesPlaceholder)
            End If

            If rewriterInfo.XmlnsAttributesPlaceholder IsNot Nothing Then
                RemovePlaceholderReplacement(rewriterInfo.XmlnsAttributesPlaceholder)
            End If

            ' See comment above
            'RemovePlaceholderReplacement(rewriterInfo.Placeholder)

            Dim secondArgumentType As TypeSymbol = constructor.Parameters(1).Type
            Dim secondArgument As BoundExpression
            If secondArgumentType.IsArrayType Then
                ' Second parameter is an object array
                Dim arrayType = DirectCast(secondArgumentType, ArrayTypeSymbol)
                secondArgument = New BoundArrayCreation(
                                        objCreation.Syntax,
                                        ImmutableArray.Create(Of BoundExpression)(
                                            New BoundLiteral(
                                                objCreation.Syntax,
                                                ConstantValue.Create(rewrittenCallArguments.Length),
                                                Me.GetSpecialType(SpecialType.System_Int32))),
                                        New BoundArrayInitialization(
                                            objCreation.Syntax, rewrittenCallArguments.AsImmutableOrNull, arrayType),
                                        arrayType)

            Else
                ' Second parameter is an object; there must be only one argument
                Debug.Assert(rewrittenCallArguments.Length = 1)
                secondArgument = rewrittenCallArguments(0)
            End If

            Return objCreation.Update(constructor,
                                      ImmutableArray.Create(Of BoundExpression)(
                                          VisitExpression(origArgument), secondArgument),
                                      defaultArguments:=Nothing,
                                      Nothing,
                                      objCreation.Type)
        End Function

        Private Function CreateTempLocalInExpressionLambda(syntax As SyntaxNode, type As TypeSymbol, expr As BoundExpression) As BoundLocal
            Dim local As New SynthesizedLocal(Me._topMethod, type, SynthesizedLocalKind.XmlInExpressionLambda, Me._currentMethodOrLambda.Syntax)
            Dim boundLocal = New BoundLocal(syntax, local, type)
            Me._xmlFixupData.AddLocal(local, New BoundAssignmentOperator(syntax, boundLocal, expr, suppressObjectClone:=True, type:=type))
            Return boundLocal.MakeRValue()
        End Function

        Private Sub CreatePrefixesAndNamespacesArrays(
                                                         rewriterInfo As BoundXmlContainerRewriterInfo,
                                                         syntax As SyntaxNode,
                                                         <Out()> ByRef prefixes As BoundExpression,
                                                         <Out()> ByRef namespaces As BoundExpression)

            Dim prefixesBuilder = ArrayBuilder(Of BoundExpression).GetInstance()
            Dim namespacesBuilder = ArrayBuilder(Of BoundExpression).GetInstance()

            For Each pair In Me._xmlImportedNamespaces
                prefixesBuilder.Add(CreateCompilerGeneratedXmlnsPrefix(syntax, pair.Key))
                namespacesBuilder.Add(CreateCompilerGeneratedXmlNamespace(syntax, pair.Value))
            Next

            For Each pair In rewriterInfo.InScopeXmlNamespaces
                prefixesBuilder.Add(CreateCompilerGeneratedXmlnsPrefix(syntax, pair.Key))
                namespacesBuilder.Add(CreateCompilerGeneratedXmlNamespace(syntax, pair.Value))
            Next

            prefixes = VisitExpressionNode(CreateCompilerGeneratedArray(syntax, rewriterInfo.PrefixesPlaceholder.Type, prefixesBuilder.ToImmutableAndFree()))
            namespaces = VisitExpressionNode(CreateCompilerGeneratedArray(syntax, rewriterInfo.NamespacesPlaceholder.Type, namespacesBuilder.ToImmutableAndFree()))
        End Sub

        Private Function BindXmlNamespace(syntax As SyntaxNode, [namespace] As BoundExpression) As BoundExpression
            ' XNamespace.Get must exist since we only add XNamespaces in
            ' lowering if corresponding XNamespaces were created when binding
            ' the containing XElement. (See Binder.BindXmlContainerRewriterInfo
            ' where we add xmlns attributes for any prefixes required from Imports.)
            Dim method = DirectCast(Compilation.GetWellKnownTypeMember(WellKnownMember.System_Xml_Linq_XNamespace__Get), MethodSymbol)
            Return New BoundCall(syntax,
                                     method,
                                     methodGroupOpt:=Nothing,
                                     receiverOpt:=Nothing,
                                     arguments:=ImmutableArray.Create([namespace]),
                                     constantValueOpt:=Nothing,
                                     type:=method.ReturnType).MakeCompilerGenerated()
        End Function

        Private Function CreateStringLiteral(syntax As SyntaxNode, str As String) As BoundLiteral
            Return New BoundLiteral(syntax, ConstantValue.Create(str), GetSpecialType(SpecialType.System_String)).MakeCompilerGenerated()
        End Function

        Private Function CreateCompilerGeneratedXmlnsPrefix(syntax As SyntaxNode, prefix As String) As BoundExpression
            Return CreateStringLiteral(syntax, If(prefix = StringConstants.DefaultXmlnsPrefix, StringConstants.XmlnsPrefix, prefix))
        End Function

        Private Function CreateCompilerGeneratedXmlNamespace(syntax As SyntaxNode, [namespace] As String) As BoundExpression
            Return BindXmlNamespace(syntax, CreateStringLiteral(syntax, [namespace]))
        End Function

        ''' <summary>
        ''' Create a BoundExpression representing an array creation initialized with the given items.
        ''' If there are zero items, the result is a BoundLiteral Nothing, otherwise, a BoundArrayCreation.
        ''' </summary>
        Private Function CreateCompilerGeneratedArray(
                                                     syntax As SyntaxNode,
                                                     arrayType As TypeSymbol,
                                                     items As ImmutableArray(Of BoundExpression)) As BoundExpression
            Dim result As BoundExpression

            If items.Length = 0 Then
                result = New BoundLiteral(syntax, ConstantValue.Nothing, arrayType)
            Else
                Dim size = (New BoundLiteral(syntax, ConstantValue.Create(items.Length), GetSpecialType(SpecialType.System_Int32))).MakeCompilerGenerated()
                Dim initializer = (New BoundArrayInitialization(syntax, items, arrayType)).MakeCompilerGenerated()
                result = New BoundArrayCreation(syntax, ImmutableArray.Create(Of BoundExpression)(size), initializer, arrayType)
            End If

            result.SetWasCompilerGenerated()
            Return result
        End Function

    End Class
End Namespace