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));
Related
I am currently working on an API.
Whenever the user marks a type with a certain attribute, I want to create a new List<int> field and loop through it, performing some operations.
Here is some relevant code:
TypeReference intTypeReference = moduleDefinition.ImportReference(typeof(int));
TypeReference listType = moduleDefinition.ImportReference(typeof(List<>));
GenericInstanceType intListType = listType.MakeGenericInstanceType(intTypeReference);
var numberList =
new FieldDefinition(
name: "Numbers",
attributes: field.Attributes,
fieldType: moduleDefinition.ImportReference(intListType));
generatedType.Fields.Add(numberList);
Type enumeratorType = typeof(List<>.Enumerator);
var enumeratorTypeReference = moduleDefinition.ImportReference(enumeratorType);
GenericInstanceType intEnumeratorType = enumeratorTypeReference.MakeGenericInstanceType(intTypeReference);
var enumeratorVariable = new VariableDefinition(intEnumeratorType);
convertMethod.Body.Variables.Add(enumeratorVariable);
ilProcessor.Emit(OpCodes.Ldarg_0); // this
ilProcessor.Emit(OpCodes.Ldfld, numberList);
MethodReference getEnumeratorMethodReference =
new MethodReference(
name: "GetEnumerator",
returnType: intEnumeratorType,
declaringType: intListType)
{
HasThis = true
};
ilProcessor.Emit(OpCodes.Callvirt, getEnumeratorMethodReference);
ilProcessor.Emit(OpCodes.Stloc, enumeratorVariable);
TypeDefinition enumeratorTypeDefinition = enumeratorTypeReference.Resolve();
MethodDefinition getCurrentMethod =
enumeratorTypeDefinition.Properties.Single(p => p.Name == "Current").GetMethod;
MethodDefinition moveNextMethod =
enumeratorTypeDefinition.Methods.Single(m => m.Name == "MoveNext");
MethodReference getCurrentMethodReference = moduleDefinition.ImportReference(getCurrentMethod);
MethodReference moveNextMethodReference = moduleDefinition.ImportReference(moveNextMethod);
// Call enumerator.Current
ilProcessor.Emit(OpCodes.Ldloc, enumeratorVariable);
ilProcessor.Emit(OpCodes.Callvirt, getCurrentMethodReference);
// Store it inside currentVariable
ilProcessor.Emit(OpCodes.Stloc, currentVariable);
ilProcessor.Emit(OpCodes.Nop);
Here is the relevant output:
List<int>.Enumerator enumerator = Numbers.GetEnumerator();
int value = (int)((List<T>.Enumerator*)(byte*)enumerator)->Current;
List<int>.Enumerator enumerator = Numbers.GetEnumerator(); is my desired result. However, int value = (int)((List<T>.Enumerator*)(byte*)enumerator)->Current; is obviously not what I want.
What should I do differently so that my output becomes int value = enumerator.Current, instead of the unreadable mess that it currently is?
List<T>.Enumerator is a value type. As such, you need to be calling methods on the address of the enumerator variable, rather than on its value.
You also can't use callvirt on value types (although you can do constrained virtual calls, which is useful for calling some methods from object). You need to use call here. This isn't a problem, because value types can't be subclassed, so you know the exact method you're calling.
Therefore you need to:
ilProcessor.Emit(OpCodes.Ldloca_S, enumeratorVariable);
ilProcessor.Emit(OpCodes.Call, getCurrentMethodReference);
This explains why you're getting the strange decompiled output: the decompiler knows that Current can only be called on the address of enumerator, but it also sees that you're actually calling it on the value, so it concocts the cast to turn enumerator into a pointer to a List<T>.Enumerator.
You can see that on SharpLab.
Can i deserialize json string to MongoDB.Driver.UpdateDefinition
I try:
var updateData="{'Description':'Big Description'}";
var obj = BsonSerializer.Deserialize<UpdateDefinition<BsonDocument>>(updateData);
But it not work(
You cannot instantiate UpdateDefinition<T> since it's an abstract class. You can get an instance of JsonUpdateDefinition which represents update operation by using below code:
var updateData = "{'Description':'Big Description'}";
var obj = new JsonUpdateDefinition<BsonDocument>(updateData);
You should also check whether you want to replace existing document using above update definition or just set single field. In that case you need $set operator.
I have some methods that execute arbitrary SQL against a database and serialize that data collection into a list of a concrete type. That data is then serialized into JSON and stored in a cell in a table. Later, I need to come back and deserialize that data back into its original collection so that it can be used.
I'm having some issues figuring out how to take a Type object and create a collection of that type in order to deserialize it. Here is how my code operates:
public async Task ExecuteWidget(Guid runGuid, string widgetName, Type type, string sql,
IEnumerable<SqlParameter> parameters)
{
var report = operationsContext.ReportRuns.FirstOrDefault(n => n.RunGuid == runGuid);
CheckReportStatus(report);
var param = parameters.ToList();
var result = edwContext.Database.SqlQuery(type, sql, param.ToArray<object>());
var query = result.GetQuery(param);
var data = await result.ToListAsync();
var widgetData = new ReportRunWidgetData()
{
ReportRunId = report?.ReportRunId ?? -1, // This should never be null.
WidgetName = widgetName,
WidgetData = new JavaScriptSerializer().Serialize(data),
Query = query
};
operationsContext.ReportRunWidgetDatas.Add(widgetData);
await operationsContext.SaveChangesAsync();
}
My fetching logic looks something like this:
public object FetchWidgetData(Guid runGuid, string widgetName, Type dataType)
{
var data = operationsContext.ReportRuns
.Include("ReportRunWidgetDatas")
.FirstOrDefault(n => n.RunGuid == runGuid)?
.ReportRunWidgetDatas.FirstOrDefault(n => n.WidgetName == widgetName)?
.WidgetData;
if (data == null) return null;
var deserialized = new JavaScriptSerializer().Deserialize(data, dataType);
return deserialized;
}
Now when the ExecuteWidget method is called, the type parameter is populated by the widget's DTO datatype. For example HeadlineWidgetDTO. However, the execute command gets the data back as a List<HeadlineWidgetDTO>. When my FetchWidgetData method is called, the dataType supplied is still HeadlineWidgetDTO, but it actually needs to be of type IEnumerable<HeadlineWidgetDTO> to deserialize properly.
Given just the type for an individual data row, how can I create a Type object that is instead a collection of that type?
This is mostly a duplicate of How to use Activator to create an instance of a generic Type and casting it back to that type?, however it's hard to tell.
Basically, if you have a type object Type theType, you need to do something like:
var listType = typeof(List<>);
var typeParams = new [] {theType};
var listOfTType = listType.MakeGenericType(typeParams);
var newListOfT = Activator.CreateInstance(listOfTType);
At that point, you have a variable of type object, but that references an object of type List<WhateverYourTypeIs>. Say, theType is typeof(int), then you will have an object of List<int>. Casting it to something usuable is a whole other question though. If you want to add something to that list, I suspect the best way would be to get a MethodInfo for the Add method and Invoke it.
I thought of another way to do this if the type has a default constructor and isn't too expensive to create. Here's a sample (creating a List<int> - but that's just the way I have it coded):
var type = typeof(int);
var dummy = Activator.CreateInstance(type);
var listOfType = new[] {dummy}.ToList();
When you are finished, the listOfType variable is typed as a List<object> but refers to a List<int>. It's mostly mostly workable - for example, you can call Add(object someObj) on it. You won't get compile type parameter type checking, but you will be able to use it.
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:
I am trying to create a code analyzer in Roslyn, and I need to analyze SqlCommand usage in a project. I have written the analyzer and it works fine when I test it out in Visual Studio project, but when I am writing unit test and I am trying to get SymbolInfo from SemanticModel and am always getting null.
What am i missing?
string test = #"public class TestClass
{
public void SomeMethod(int x)
{
var command = new SqlCommand(""Some COmmabnd"",new SqlConnection(""conn string""));
command.ExecuteReader();
}
}";
var tree = CSharpSyntaxTree.ParseText(test);
var systemDataReference = MetadataReference.CreateFromFile(typeof(System.Data.IDbCommand).Assembly.Location);
var systemConfigurationReference = MetadataReference.CreateFromFile(typeof(ConfigurationManager).Assembly.Location);
var systemTransactionReference = MetadataReference.CreateFromFile(typeof(Transaction).Assembly.Location);
var systemXmlnReference = MetadataReference.CreateFromFile(typeof(XPathDocument).Assembly.Location);
var system = MetadataReference.CreateFromFile(typeof(Uri).Assembly.Location);
var mscorRef = MetadataReference.CreateFromFile(typeof(object).Assembly.Location);
var systemCore = MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location);
var systemNumerics = MetadataReference.CreateFromFile(typeof(BigInteger).Assembly.Location);
var compilation = CSharpCompilation.Create("TestCompilatin", new[] {tree},
new[]
{
mscorRef, system, systemXmlnReference, systemTransactionReference, systemDataReference,
systemConfigurationReference,systemCore,systemNumerics
});
var semanticModel = compilation.GetSemanticModel(tree);
var invocationExpressions = tree.GetRoot().DescendantNodes().OfType<InvocationExpressionSyntax>();
foreach (var invocationExpressionSyntax in invocationExpressions)
{
var memeber = invocationExpressionSyntax.Expression as MemberAccessExpressionSyntax;
var symbolInfo = semanticModel.GetSymbolInfo(memeber);
}
semanticModel.GetSymbolInfo() returns SymbolInfo with null symbol for anything that I try.
Another possible cause is that the source file for the invoked method has not been syntactically parsed and added to the compilation. You can always get some information about the method being invoked in the caller's syntax tree, but unless the invoked method has already been added to the compilation, it won't be available in the semantic model. This is painfully obvious after thinking about it for a bit. Of course the semantic model will not have information about a method that has not been processed. It is possible to add multiple syntax trees to a single compilation using AddSyntaxTrees() method. Note that this method returns a new compilation instance.
Hopefully this will save someone a little bit of time and I haven't embarrassed myself too much by posting this answer.