-
-
Save AdamSpeight2008/4109dfc839b9bd5ff86ed3aa7dc3322c to your computer and use it in GitHub Desktop.
Get some statistics on the frequency of scoping usage of `out var`
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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