87 lines
3.3 KiB
C#
87 lines
3.3 KiB
C#
namespace BizHawk.Analyzers;
|
|
|
|
using System.Collections.Immutable;
|
|
|
|
using Microsoft.CodeAnalysis;
|
|
using Microsoft.CodeAnalysis.Diagnostics;
|
|
using Microsoft.CodeAnalysis.Operations;
|
|
|
|
[DiagnosticAnalyzer(LanguageNames.CSharp)]
|
|
public sealed class TernaryInferredTypeMismatchAnalyzer : DiagnosticAnalyzer
|
|
{
|
|
private static readonly DiagnosticDescriptor DiagTernaryInferredTypeMismatch = new(
|
|
id: "BHI1210",
|
|
title: "Inferred type of branches of ternary expression in interpolation don't match",
|
|
messageFormat: "{0}",
|
|
category: "Usage",
|
|
defaultSeverity: DiagnosticSeverity.Warning,
|
|
isEnabledByDefault: true);
|
|
|
|
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; } = ImmutableArray.Create(DiagTernaryInferredTypeMismatch);
|
|
|
|
public override void Initialize(AnalysisContext context)
|
|
{
|
|
context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
|
|
context.EnableConcurrentExecution();
|
|
context.RegisterCompilationStartAction(initContext =>
|
|
{
|
|
initContext.RegisterOperationAction(oac =>
|
|
{
|
|
var ifelseOrTernaryOp = (IConditionalOperation) oac.Operation;
|
|
if (ifelseOrTernaryOp.WhenFalse is null) return;
|
|
var parent = ifelseOrTernaryOp.Parent!;
|
|
if (parent.Kind is OperationKind.Conversion) parent = parent.Parent!;
|
|
if (parent.Kind is not OperationKind.Interpolation) return;
|
|
var ternaryOp = ifelseOrTernaryOp;
|
|
var typeTernary = ternaryOp.Type!;
|
|
#if false // never hit; either both branches are string and there are no conversions, or conversions are necessary
|
|
if (typeTernary.SpecialType is SpecialType.System_String) return;
|
|
#endif
|
|
var lhs = ternaryOp.WhenTrue;
|
|
var rhs = ternaryOp.WhenFalse;
|
|
|
|
static IOperation TrimImplicitCast(IOperation op)
|
|
=> op is IConversionOperation { Conversion.IsImplicit: true } implCastOp ? implCastOp.Operand : op;
|
|
var typeLHS = TrimImplicitCast(lhs).Type!;
|
|
var typeRHS = TrimImplicitCast(rhs).Type!;
|
|
if (typeLHS.Matches(typeRHS)) return; // unnecessary conversion operators on each branch? seen with `? this : this`
|
|
|
|
const string ERR_MSG_OBJECT = "missing ToString means ternary branches are upcast to object";
|
|
var fatal = false;
|
|
IOperation flaggedOp = ternaryOp;
|
|
string message;
|
|
if (typeLHS.SpecialType is SpecialType.System_String)
|
|
{
|
|
flaggedOp = rhs;
|
|
message = ERR_MSG_OBJECT;
|
|
}
|
|
else if (typeRHS.SpecialType is SpecialType.System_String)
|
|
{
|
|
flaggedOp = lhs;
|
|
message = ERR_MSG_OBJECT;
|
|
}
|
|
else if (typeTernary.SpecialType is SpecialType.System_Object)
|
|
{
|
|
fatal = true;
|
|
message = "ternary branches are upcast to object! add ToString calls, or convert one to the other's type";
|
|
}
|
|
else
|
|
{
|
|
// if one's already an e.g. int literal, flag the e.g. char literal
|
|
if (typeTernary.Matches(typeLHS)) flaggedOp = rhs;
|
|
else if (typeTernary.Matches(typeRHS)) flaggedOp = lhs;
|
|
message = $"ternary branches are converted to {typeTernary} before serialisation, possibly unintended";
|
|
}
|
|
oac.ReportDiagnostic(Diagnostic.Create(
|
|
DiagTernaryInferredTypeMismatch,
|
|
flaggedOp.Syntax.GetLocation(),
|
|
fatal ? DiagnosticSeverity.Error : DiagnosticSeverity.Warning,
|
|
additionalLocations: null,
|
|
properties: null,
|
|
messageArgs: message));
|
|
},
|
|
OperationKind.Conditional);
|
|
});
|
|
}
|
|
}
|