Roslyn VSIX Convert local variable to global const - c#

I'm making a simple Roslyn Extensions to look for potential consts and make them global. I've written code to convert to a local const first which is working fine:
I have a codeFixProvider who registers a my fix like so:
context.RegisterCodeFix(CodeAction.Create(title: title,
createChangedDocument: c => this.MakeConstAsync(context.Document, declaration, c),
equivalenceKey: title), diagnostic);
My Make Const function looks like so:
private async Task<Document> MakeConstAsync(Document document, LocalDeclarationStatementSyntax localDeclaration, CancellationToken cancellationToken)
{
// Remove the leading trivia from the local declaration.
var firstToken = localDeclaration.GetFirstToken();
var leadingTrivia = firstToken.LeadingTrivia;
var trimmedLocal = localDeclaration.ReplaceToken(
firstToken, firstToken.WithLeadingTrivia(SyntaxTriviaList.Empty));
localDeclaration.GetNearestParent<ClassDeclarationSyntax>();
// Create a const token with the leading trivia.
var constToken = SyntaxFactory.Token(leadingTrivia, SyntaxKind.ConstKeyword, SyntaxFactory.TriviaList(SyntaxFactory.ElasticMarker));
// Insert the const token into the modifiers list, creating a new modifiers list.
var newModifiers = trimmedLocal.Modifiers.Insert(0, constToken);
// If the type of the declaration is 'var', create a new type name
// for the inferred type.
var variableDeclaration = localDeclaration.Declaration;
var variableTypeName = variableDeclaration.Type;
if (variableTypeName.IsVar)
{
var semanticModel = await document.GetSemanticModelAsync(cancellationToken);
// Special case: Ensure that 'var' isn't actually an alias to another type
// (e.g. using var = System.String).
var aliasInfo = semanticModel.GetAliasInfo(variableTypeName);
if (aliasInfo == null)
{
// Retrieve the type inferred for var.
var type = semanticModel.GetTypeInfo(variableTypeName).ConvertedType;
// Special case: Ensure that 'var' isn't actually a type named 'var'.
if (type.Name != "var")
{
// Create a new TypeSyntax for the inferred type. Be careful
// to keep any leading and trailing trivia from the var keyword.
var typeName = SyntaxFactory.ParseTypeName(type.ToDisplayString())
.WithLeadingTrivia(variableTypeName.GetLeadingTrivia())
.WithTrailingTrivia(variableTypeName.GetTrailingTrivia());
// Add an annotation to simplify the type name.
var simplifiedTypeName = typeName.WithAdditionalAnnotations(Simplifier.Annotation);
// Replace the type in the variable declaration.
variableDeclaration = variableDeclaration.WithType(simplifiedTypeName);
}
}
}
var root = await document.GetSyntaxRootAsync(cancellationToken);
// Produce the new local declaration.
var newLocal = trimmedLocal.WithModifiers(newModifiers)
.WithDeclaration(variableDeclaration);
// Add an annotation to format the new local declaration.
var formattedLocal = newLocal.WithAdditionalAnnotations(Formatter.Annotation);
// Replace the old local declaration with the new local declaration.
var newRoot = root.ReplaceNode(localDeclaration, formattedLocal);
// Return document with transformed tree.
return document.WithSyntaxRoot(newRoot);
}
All of this work fine like so, but when I introduce a function to create a FieldDeclaration, the next time I enter the hive, to check the syntax analysis, I hit Ctrl + . and before anything pop up I get an ambigous One Or More Error occured modal.
public FieldDeclarationSyntax ConvertToFieldDeclarationSyntax(SyntaxTokenList modifiers, VariableDeclarationSyntax declaration, SyntaxKind syntaxKind = SyntaxKind.PublicKeyword)
{
return null;
//return SyntaxFactory.FieldDeclaration(new SyntaxList<AttributeListSyntax>(), modifiers, declaration);
}
I'm not sure why but that function above throws causes an error, I never even reference, for some reason I guess loading that assembly conflicts. Does anyone know how to make FieldDeclarationSyntax in Roslyn?
EDIT
So I turned on all exceptions and now I'm seeing that I get an error
Unable to cast object of type 'System.Reflection.RuntimeMethodInfo' to type 'System.Reflection.ConstructorInfo'.
When Inspecting this now I see additonal error with the stack trace here:
This occurs when hitting ctrl + . on my highlighted syntax error rule a modal pops up from the hive:

Related

c# DynamicMethod exception

I have the following code
var dynamicAdd2 = new DynamicMethod("add",
typeof(string),
new[] { typeof(TestType) },
typeof(Program).Module, false);
var add2Body = typeof(Program).GetMethod("add2").GetMethodBody().GetILAsByteArray();
var dynamicIlInfo = dynamicAdd2.GetDynamicILInfo();
var ilGenerator = dynamicAdd2.GetILGenerator();
dynamicIlInfo.SetLocalSignature(SignatureHelper.GetLocalVarSigHelper().GetSignature());
dynamicIlInfo.SetCode(add2Body, 1000);
var test2 = (Func<TestType, string>)dynamicAdd2.CreateDelegate(typeof(Func<TestType, string>));
var ret2 = test2(new TestType()); // <-- Exception
the add2:
public string add2(TestType digit)
{
return digit.Name;
}
the testType:
public class TestType
{
public string Name = "test";
}
I get a InvalidProgrammException, no more information
So I expect that the creation of the dynamic method fails. I think the dynamic Method can not find the references to the TestClass. Or what can be wrong in this case? Or what can I do to get a hint where the problem lies? the Exception brings not the needed infos...
You cannot directly copy IL stream from existing method to dynamic method, because IL uses so called tokens (32-bit numbers) to represent types, methods or fields. For the same field, value of token can be different in different modules, so byte-copying method IL stream without replacing tokens results in invalid program.
Second problem is that because add2 is instance method (not static), you must add instance of type that this method belongs to as first argument of method. In C# this first argument of instance methods is hidden, but IL requires it. Or you can declare method as static to avoid this.
Third problem is that add2 method contains (compiler generated) local variable. You have to add this variable to local signature (using SetLocalSignature() method), otherwise your method would use undeclared variable. (See code bellow to see how to do that).
Solution 1:
First solution is to use GetILGenerator() instead of GetDynamicILInfo(), and rewrite IL stream from scratch. You can use IL disassembler (e.g. ILDASM, .NET Reflector) to get list of instructions for any existing method. Writing these instructions to IlGenerator using IlGenerator.Emit(...) should not be difficult.
static void Main(string[] args)
{
var dynamicAdd2 = new DynamicMethod("add",
typeof(string),
new[] { typeof(Program), typeof(TestType) },
typeof(Program).Module,
false);
var ilGenerator = dynamicAdd2.GetILGenerator();
ilGenerator.DeclareLocal(typeof(string));
ilGenerator.Emit(OpCodes.Ldarg_1);
var fld = typeof(TestType).GetField("Name");
ilGenerator.Emit(OpCodes.Ldfld, fld);
ilGenerator.Emit(OpCodes.Ret);
var test2 = (Func<TestType, string>)dynamicAdd2.CreateDelegate(typeof(Func<TestType, string>), new Program());
var ret2 = test2(new TestType());
}
Solution 2:
If you cannot use IlGenerator and you require direct IL stream manipulation using GetDynamicILInfo, you have to replace tokens in IL stream with values that are valid for generated dynamic method. Replacing tokens generally requires you to know offsets of these tokens in IL stream. Problem is that exact offset depends on compiler (and is even different for Release/Debug build). So you either have to use some IL dissassembler to get these offsets, or write IL parser able to do that (which is not trivial, maybe you can find some library for that). So following code uses kind of "dirty hack" to make it work in this particular case, but does not work generally.
public static void Main()
{
var dynamicAdd2 = new DynamicMethod("add",
typeof(string),
new[] { typeof(Program), typeof(TestType) },
typeof(Program).Module,
false);
var add2Body = typeof(Program).GetMethod("add2").GetMethodBody();
var add2ILStream = add2Body.GetILAsByteArray();
var dynamicIlInfo = dynamicAdd2.GetDynamicILInfo();
var token = dynamicIlInfo.GetTokenFor(typeof(TestType).GetField("Name").FieldHandle);
var tokenBytes = BitConverter.GetBytes(token);
//This tries to find index of token used by ldfld by searching for it's opcode (0x7B) in IL stream.
//Token follows this instructions so I add +1. This works well for this simple method, but
//will not work in general case, because IL stream could contain 0x7B on other unrelated places.
var tokenIndex = add2ILStream.ToList().IndexOf(0x7b) + 1;
Array.Copy(tokenBytes, 0, add2ILStream, tokenIndex, 4);//
//Copy signature of local variables from original add2 method
var localSignature = SignatureHelper.GetLocalVarSigHelper();
var localVarTypes = add2Body.LocalVariables.Select(_ => _.LocalType).ToArray();
localSignature.AddArguments(localVarTypes, null, null);
dynamicIlInfo.SetLocalSignature(localSignature.GetSignature());
dynamicIlInfo.SetCode(add2ILStream, 1);
var test2 = (Func<TestType, string>)dynamicAdd2.CreateDelegate(typeof(Func<TestType, string>));
var ret2 = test2(new TestType());
}

Can not implicity convert type Systems.Collection.Generic.List<feedEventViewModel> to Proj.Areas.Management.Models.FeedEventViewModel

I'm trying to convert a view model to list and then return it to the view but am getting the cannot implicity convert type error.
Code:
public ActionResult Index(FeedEventCommand command)
{
var feedEventViewModel = new FeedEventViewModel
{
AnimalId = command.AnimalId,
AnimalName = command.AnimalName,
FeederTypeId = command.FeederTypeId,
FeederType = command.FeederType
};
feedEventViewModel = new List<feedEventViewModel>(); <--Error line
return View(feedEventViewModel);
}
What am I doing wrong in this case?
feedEventViewModel is already declared as a single object, you can't declare it again as a List<FeedEventViewModel>(). Other language such as Rust allows you to "shadow" the variable declaration but C# not (and var is just a shorter way to declare a variable).
You can solve this issue quite easily:
return View( new List<FeedEventViewModel>() {
new FeedEventViewModel{
AnimalId = command.AnimalId,
AnimalName = command.AnimalName,
FeederTypeId = command.FeederTypeId,
FeederType = command.FeederType
}
}
);
You may be misunderstanding what the var keyword is doing here. When you declare a variable with var you are not saying that the variable can be anything, you are saying to the compiler that it can work out what the type is without you needing to specify it precisely.
So in your example (or a slight modification) when the compiler encounters the code:
var feedEventViewModel = new FeedEventViewModel();
it will see that the right hand side of the assignment is of type FeedEventViewModel and so the variable feedEventViewModel will be of that type. In effect it will be like you typed:
FeedEventViewModel feedEventViewModel = new FeedEventViewModel();
Any later use of that variable must be in line with this declaration so when you do feedEventViewModel = new List<feedEventViewModel>(); the compiler rightly says that List<feedEventViewModel> is not of the type expected by feedEventViewModel. There is such a thing as implicit conversions whereby the compiler knows how to convert between two different types and is allowed to do it without it being specifically requested but no such implicit conversions were found, hence the error.
It is unclear from the information given what exactly you are doing (is the item you created meant to be on the list? Does your view expect a list or a single item?). If you need a list with the item in then I'd just go with:
var list = new List<feedEventViewModel>(){feedEventViewModel };
return View(list);

Initialize dynamically the var filter

What I'm trying to achieve is to initialize the "filter" variable dynamically based on what my method gets.
Initializing it to null throws an error.
Leaving it empty throws error.
Setting it to a generic type throws an error
Setting it to a new BsonDocument also throws an error
This is my code:
var filter=null;
if (id != 0)
if (subID != 0)
//Get Specific Categories
filter = builder.Eq("Categories.Sub.id", id) & builder.Eq("Categories.Sub.Custom.id", subID);
else
//Get SubCategories
filter = builder.Eq("Categories.Sub.id", id);
else
//Get Generic Categories
filter = new BsonDocument();
I've been searching but nobody seems to have my problem or I'm not able to find it.
Var is not a dynamic variable, it is a keyword for type inference. These are very different concepts. The key issue is that in your code snippet the compiler can not figure out what kind of variable you want your var to be.
var myNumber = 3; // myNumber is inferred by the compiler to be of type int.
int myNumber = 3; // this line is considered by the computer to be identical to the one above.
The inferred type of a var variable does not change.
var myVariable = 3;
myVariable = "Hello"; // throws an error because myVariable is of type int
The type of dynamic variables can change.
dynamic myVariable = 3;
myVariable = "Hello"; // does not throw an error.
The compiler must be able to determine the type of an object when a var variable is created;
var myVariable = null; // null can be anything, the compiler can not figure out what kind of variable you want.
var myVariable = (BsonDocument)null; // by setting the variable to a null instance of a specific type the compiler can figure out what to set var to.
With var it's an implicit type and you can initialize a implicit type variable to null since it can be both value type and reference type; and value type can't be assigned to null (unless otherwise it's made nullable explicitly).
Thus instead of saying var filter=null; you should explicitly specify the type
BsonDocument filter = null;

How to generate code with short type names instead of full type names?

Assume I want to create a code file with a class that contains a field which has some non-primitive non-aliased type. Let it be something well-known: StreamWriter type.
I add System.IO import to allow short type reference. I create type reference this way:
var type = new CodeTypeReference(typeof(StreamWriter));
But redundant type prefix is still generated:
// ------------------------------------------------------------------------------
// <autogenerated>
// This code was generated by a tool.
// Mono Runtime Version: 2.0.50727.1433
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </autogenerated>
// ------------------------------------------------------------------------------
namespace Bar {
using System.IO;
public class Foo {
private System.IO.StreamWriter baz;
}
}
I know I can work around using string:
var type = new CodeTypeReference("StreamWriter");
// or even
var type = new CodeTypeReference(typeof(StreamWriter).Name);
But there should be some nice build in CodeDom way, shouldn't it?
P.S.: Second workaround may look type-safe enough, but I don't know concrete type, only that it's derived from known base type. So, actually it would look like this:
var type = new CodeTypeReference(controlData.Control.GetType().Name);
It'll produce ivalid code if the control has some “weird” type like generic or nested type.
If you look at how CodeTypeReference construction from Type is implemented, you'll see that you can implement something like this as workaround:
public static CodeTypeReference CreateShortCodeTypeReference(Type type, CodeNamespaceImportCollection imports)
{
var result = new CodeTypeReference(type);
Shortify(result, type, imports);
return result;
}
private static void Shortify(CodeTypeReference typeReference, Type type, CodeNamespaceImportCollection imports)
{
if (typeReference.ArrayRank > 0)
{
Shortify(typeReference.ArrayElementType, type, imports);
return;
}
if (type.Namespace != null && imports.Cast<CodeNamespaceImport>()
.Any(cni => cni.Namespace == type.Namespace))
{
var prefix = type.Namespace + '.';
if (prefix != null)
{
var pos = typeReference.BaseType.IndexOf(prefix);
if (pos == 0)
{
typeReference.BaseType = typeReference.BaseType.Substring(prefix.Length);
}
}
}
}
Usage:
var type = CreateShortCodeTypeReference(foo.GetType(), codeNamespace.Imports);
Result:
Assuming using UnityEngine.UI; is present:
UnityEngine.UI.Button → Button
UnityEngine.UI.Button[] → Button[]
UnityEngine.UI.Button[][,,][] → Button[][,,][]
Limitations:
Won't affect generic type parameters like List<System.IO.Path>.
Valid only for generating code in those CLR-languages that support import (like C# and VB).

How should we add an instantiation to an existing variable?

I have written an Analyzer that correctly detects uninstantiated collections. Now I'm writing the appropriate CodeFixProvider that will give the option to instantiate it.
When I execute my code and look at the provided fix, it would simply remove the identifier and only keep the type. Where did I go wrong in my approach?
public async Task<IEnumerable<CodeAction>> GetFixesAsync(Document document, TextSpan span, IEnumerable<Diagnostic> diagnostics, CancellationToken cancellationToken)
{
var root = await document.GetSyntaxRootAsync(cancellationToken);
var token = root.FindToken(span.Start);
document.TryGetSemanticModel(out var semanticModel);
var statement = token.Parent.Parent as VariableDeclarationSyntax;
// Construct variable declaration
var declarator = new SeparatedSyntaxList<VariableDeclaratorSyntax>();
var identifier = statement.Variables[0].Identifier;
var newType = semanticModel.GetTypeInfo(statement.Type).Type;
var newObject = SyntaxFactory.ObjectCreationExpression(type: SyntaxFactory.ParseTypeName(newType.Name));
var equalsClause = SyntaxFactory.EqualsValueClause(newObject);
declarator.Add(SyntaxFactory.VariableDeclarator(identifier, null, equalsClause));
var newStatement = SyntaxFactory.VariableDeclaration(statement.Type, declarator);
var newRoot = root.ReplaceNode(statement, newStatement);
return new[]
{
CodeAction.Create("Instantiate collection variable", document.WithSyntaxRoot(newRoot))
};
}
You have to remember that all types in Roslyn are immutable. That means that even operations like Add() don't actually modify the object they're called on, they return the modified object.
This means that after Add(), you have to work with the returned value, not the original object.
If you modify your code to the following, it will work:
declarator = declarator.Add(SyntaxFactory.VariableDeclarator(identifier, null, equalsClause));

Categories