Skip to content

Instantly share code, notes, and snippets.

@AdamSpeight2008
Created November 22, 2016 07:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AdamSpeight2008/4109dfc839b9bd5ff86ed3aa7dc3322c to your computer and use it in GitHub Desktop.
Save AdamSpeight2008/4109dfc839b9bd5ff86ed3aa7dc3322c to your computer and use it in GitHub Desktop.
Get some statistics on the frequency of scoping usage of `out var`
Imports Microsoft.CodeAnalysis.CSharp
Imports Microsoft.CodeAnalysis.CSharpExtensions
Imports Microsoft.CodeAnalysis.CSharp.Syntax
Module Module1
Sub Main()
Dim Tally_Widening As Int32 = 0, Tally_Narrowing As Int32 = 0
Dim repo As String = "L:\Source\Source\Repos\roslyn-AdamSpeight2008\src\"
For Each file In IO.Directory.EnumerateFiles(repo, "*.cs", IO.SearchOption.AllDirectories) '.AsParallel
Console.WriteLine(file.Substring(repo.Length))
Dim tree = CSharpSyntaxTree.ParseText(IO.File.ReadAllText(file))
Dim root = tree.GetRoot
Dim Mscorlib = MetadataReference.CreateFromFile(GetType(Object).Assembly.Location)
Dim Compilation = CSharpCompilation.Create("MyCompilation", syntaxTrees:={tree}, references:={Mscorlib})
For Each argNode In root.DescendantNodes.OfType(Of CSharp.Syntax.ArgumentSyntax)
' Immediately bail if this Is Not an out-argument. If it's not an out-argument
' we clearly can't convert it to be an out-variable-declaration.
If argNode.RefOrOutKeyword.Kind <> SyntaxKind.OutKeyword Then Continue For
Dim argExpr = argNode.Expression
If argExpr.Kind <> SyntaxKind.IdentifierName Then Continue For
Dim argList = TryCast(argNode.Parent, ArgumentListSyntax)
If argList Is Nothing Then Continue For
Dim invocationCreation = argList.Parent
If Not invocationCreation.IsKind(SyntaxKind.InvocationExpression) AndAlso
Not invocationCreation.IsKind(SyntaxKind.ObjectCreationExpression) Then
' Out-variables are only legal with invocations And object creations.
' If we don't have one of those bail. Note: we need hte parent to be
' one of these forms so we can accurately verify that inlining the
' variable doesn't change semantics.
Continue For
End If
Dim identifierName = TryCast(argExpr, CSharp.Syntax.IdentifierNameSyntax)
Dim containingStatement = argExpr.FirstAncestorOrSelf(Of StatementSyntax)
If containingStatement Is Nothing Then Continue For
Dim sm = Compilation.GetSemanticModel(tree)
Dim outSymbol = sm.GetSymbolInfo(argExpr).Symbol
If outSymbol Is Nothing OrElse outSymbol.Kind <> SymbolKind.Local Then
' The out-argument wasn't referencing a local. So we don't have an local
' declaration that we can attempt to inline here.
Continue For
End If
' Ensure that the local-symbol actually belongs to LocalDeclarationStatement.
' Trying to do things Like inline a var-decl in a for-statement Is just too
' esoteric And would make us have to write a lot more complex code to support
' that scenario.
Dim localReference = outSymbol.DeclaringSyntaxReferences.FirstOrDefault
Dim localDeclarator = TryCast(localReference?.GetSyntax(), VariableDeclaratorSyntax)
If localDeclarator Is Nothing Then Continue For
Dim localDeclaration = TryCast(localDeclarator.Parent, VariableDeclarationSyntax)
Dim localStatement = TryCast(localDeclaration?.Parent, LocalDeclarationStatementSyntax)
If localStatement Is Nothing Then Continue For
If localDeclarator.SpanStart >= argNode.SpanStart Then
' We have an error situation where the local was declared after the out-var.
' Don't even bother offering anything here.
Continue For
End If
' If the local has an initializer, only allow the refactoring if it Is initialized
' with a simple literal Or 'default' expression. i.e. it's ok to inline "var v = 0"
' since there are no side-effects of the initialization. However something Like
' "var v = M()" shoudl Not be inlined as that could break program semantics.
If localDeclarator.Initializer IsNot Nothing Then
If TypeOf localDeclarator.Initializer.Value IsNot LiteralExpressionSyntax AndAlso
TypeOf localDeclarator.Initializer.Value IsNot DefaultExpressionSyntax Then
Continue For
End If
End If
' Get the block that the local Is scoped inside of. We'll search that block
' for references to the local to make sure that no reads/writes happen before
' the out-argument. If there are any reads/writes we can't inline as those
' accesses will become invalid.
Dim enclosingBlockOfLocalStatement = TryCast(localStatement.Parent, BlockSyntax)
If enclosingBlockOfLocalStatement Is Nothing Then Continue For
' Find the scope that the out-declaration variable will live in after we
' rewrite things.
Dim outArgScope = GetOutArgumentScope(argExpr)
' Make sure that variable is not accessed outside of that scope.
Dim dataFlow = sm.AnalyzeDataFlow(outArgScope)
If dataFlow.ReadOutside.Contains(outSymbol) OrElse
dataFlow.WrittenOutside.Contains(outSymbol) Then
' The variable Is read Or written from outside the block that the New variable
' would be scoped in. This would cause a break.
If IsAccessed(sm, outSymbol, enclosingBlockOfLocalStatement, localStatement, argNode) Then
Tally_Widening += 1
Continue For
End If
Continue For
End If
' Make sure the variable isn't ever acessed before the usage in this out-var.
If IsAccessed(sm, outSymbol, enclosingBlockOfLocalStatement, localStatement, argNode) Then
Continue For
End If
Tally_Narrowing += 1
Next
Next
Console.WriteLine($"Widening: {Tally_Widening,4} Narrowing: {Tally_Narrowing,4}")
End Sub
Private Function GetOutArgumentScope(argExpr As SyntaxNode) As SyntaxNode
Dim curr = argExpr
While curr IsNot Nothing
Dim lambda = TryCast(curr, LambdaExpressionSyntax)
If (lambda IsNot Nothing) AndAlso (curr.Equals(lambda.Body)) Then
Return curr
End If
If TypeOf curr Is StatementSyntax Then
If curr.Parent.IsKind(SyntaxKind.Block) Then Return curr.Parent
Return curr
End If
curr = curr.Parent
End While
Return Nothing
End Function
Private Function IsAccessed(
sm As SemanticModel,
outSymbol As ISymbol,
enclosingBlockOfLocalStatement As BlockSyntax,
localStatement As LocalDeclarationStatementSyntax,
argNode As ArgumentSyntax
) As Boolean
Dim localStatementStart = localStatement.Span.Start
Dim argNodeStart = argNode.Span.Start
Dim variableName = outSymbol.Name
For Each descendentNode In enclosingBlockOfLocalStatement.DescendantNodes
Dim descendentStart = descendentNode.Span.Start
If descendentStart <= localStatementStart Then Continue For
If descendentStart >= argNodeStart Then Exit For
If descendentNode.IsKind(SyntaxKind.IdentifierName) Then
Dim identifierName = DirectCast(descendentNode, IdentifierNameSyntax)
If identifierName.Identifier.ValueText = variableName Then
Dim symbol = sm.GetSymbolInfo(identifierName).Symbol
If outSymbol.Equals(symbol) Then Return True
End If
End If
Next
Return False
End Function
End Module
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment