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
.