I'm trying to define a struct which uses a variable with a restricted range of numbers, and implicit coercion from ints. I'd like to be able to force build errors if any constants or other hardcoded values are used with this struct.
Here is an example of what I'm trying to accomplish.
byte a = 123; // Allowed
byte b = 123123; // Not allowed
const int x = 123;
const int y = 123123;
byte c = x; // Allowed
byte d = y; // Not allowed
I would ideally like to be able to, for example, restrict a number from 1 to 99, so that MyStruct s = 50; works but MyStruct s = 150; causes a compile time error like the bytes b and d above do.
I found something similar for a different language, but not for C#.
I think you can do this by using custom attributes and roslyn code analyses. Let me sketch a solution. This should at least solve the first usecase where you initialize with a literal.
First you would need a custom attribute that applies to your struct to allow the code analyses to be able to know the valid range:
[AttributeUsage(System.AttributeTargets.Struct)]
public class MinMaxSizeAttribute : Attribute
{
public int MinVal { get; set;}
public int MaxVal { get; set;}
public MinMaxSizeAttribute()
{
}
}
What you do here is you store the min and max value in an attribute. That way you can use this later in the source code analyses.
Now apply this attribute to the struct declaration:
[MinMaxSize(MinVal = 0, MaxVal = 100)]
public struct Foo
{
//members and implicit conversion operators go here
}
Now the type information for the struct Foo
contains the value range. The next thing you need is a DiagnosticAnalyzer
to analyze your code.
public class MyAnalyzer : DiagnosticAnalyzer
{
internal static DiagnosticDescriptor Rule = new DiagnosticDescriptor("CS00042",
"Value not allowed here",
@"Type {0} does not allow Values in this range",
"type checker",
DiagnosticSeverity.Error,
isEnabledByDefault: true, description: "Value to big");
public MyAnalyzer()
{
}
#region implemented abstract members of DiagnosticAnalyzer
public override void Initialize(AnalysisContext context)
{
context.RegisterSyntaxNodeAction(AnalyzeSyntaxTree, SyntaxKind.SimpleAssignmentExpression);
}
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);
#endregion
private static void AnalyzeSyntaxTree(SyntaxNodeAnalysisContext context)
{
}
}
This is the bare bone skeleton to participate in code analyzes. The analyzer registers to analyze assignments:
context.RegisterSyntaxNodeAction(AnalyzeSyntaxTree, SyntaxKind.SimpleAssignmentExpression);
For variable declarations you would need to register for a different SyntaxKind
but for simplicity I will stick to one here.
Lets have a look at the analyses logic:
private static void AnalyzeSyntaxTree(SyntaxNodeAnalysisContext context)
{
if (context.Node.IsKind(SyntaxKind.SimpleAssignmentExpression))
{
var assign = (AssignmentExpressionSyntax)context.Node;
var leftType = context.SemanticModel.GetTypeInfo(assign.Left).GetType();
var attr = leftType.GetCustomAttributes(typeof(MinMaxSizeAttribute), false).OfType<MinMaxSizeAttribute>().FirstOrDefault();
if (attr != null && assign.Right.IsKind(SyntaxKind.NumericLiteralExpression))
{
var numLitteral = (LiteralExpressionSyntax)assign.Right;
var t = numLitteral.Token;
if (t.Value.GetType().Equals(typeof(int)))
{
var intVal = (int)t.Value;
if (intVal > attr.MaxVal || intVal < attr.MaxVal)
{
Diagnostic.Create(Rule, assign.GetLocation(), leftType.Name);
}
}
}
}
}
What the analyzer does is, is checking if the type on the left side has a MinMaxSize
associated with it and if so it checks if the right side is a literal. When it is a literal it tries to get the integer value and compares it to the MinVal
and MaxVal
associated with the type. If the values exceeds that range it will report a diagnostics error.
Please note that all this code is mostly untested. It compiles and passed some basic tests. But it is only meant to illustrate a possible solution. For further information have a look at the Rsolyn Docs
The second case you want to covers is more complex because you will need to apply dataflow analyzes to get the value of x
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With