MustBeVariantAnalyzer.cs 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. using System.Collections.Immutable;
  2. using System.Diagnostics;
  3. using System.Linq;
  4. using Microsoft.CodeAnalysis;
  5. using Microsoft.CodeAnalysis.CSharp;
  6. using Microsoft.CodeAnalysis.CSharp.Syntax;
  7. using Microsoft.CodeAnalysis.Diagnostics;
  8. namespace Godot.SourceGenerators
  9. {
  10. [DiagnosticAnalyzer(LanguageNames.CSharp)]
  11. public class MustBeVariantAnalyzer : DiagnosticAnalyzer
  12. {
  13. public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
  14. => ImmutableArray.Create(
  15. Common.GenericTypeArgumentMustBeVariantRule,
  16. Common.GenericTypeParameterMustBeVariantAnnotatedRule,
  17. Common.TypeArgumentParentSymbolUnhandledRule);
  18. public override void Initialize(AnalysisContext context)
  19. {
  20. context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None);
  21. context.EnableConcurrentExecution();
  22. context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.TypeArgumentList);
  23. }
  24. private void AnalyzeNode(SyntaxNodeAnalysisContext context)
  25. {
  26. // Ignore syntax inside comments
  27. if (IsInsideDocumentation(context.Node))
  28. return;
  29. var typeArgListSyntax = (TypeArgumentListSyntax)context.Node;
  30. // Method invocation or variable declaration that contained the type arguments
  31. var parentSyntax = context.Node.Parent;
  32. Debug.Assert(parentSyntax != null);
  33. var sm = context.SemanticModel;
  34. var typeCache = new MarshalUtils.TypeCache(context.Compilation);
  35. for (int i = 0; i < typeArgListSyntax.Arguments.Count; i++)
  36. {
  37. var typeSyntax = typeArgListSyntax.Arguments[i];
  38. // Ignore omitted type arguments, e.g.: List<>, Dictionary<,>, etc
  39. if (typeSyntax is OmittedTypeArgumentSyntax)
  40. continue;
  41. var typeSymbol = sm.GetSymbolInfo(typeSyntax).Symbol as ITypeSymbol;
  42. Debug.Assert(typeSymbol != null);
  43. var parentSymbol = sm.GetSymbolInfo(parentSyntax).Symbol;
  44. if (!ShouldCheckTypeArgument(context, parentSyntax, parentSymbol, typeSyntax, typeSymbol, i))
  45. {
  46. return;
  47. }
  48. if (typeSymbol is ITypeParameterSymbol typeParamSymbol)
  49. {
  50. if (!typeParamSymbol.GetAttributes().Any(a => a.AttributeClass?.IsGodotMustBeVariantAttribute() ?? false))
  51. {
  52. Common.ReportGenericTypeParameterMustBeVariantAnnotated(context, typeSyntax, typeSymbol);
  53. }
  54. continue;
  55. }
  56. var marshalType = MarshalUtils.ConvertManagedTypeToMarshalType(typeSymbol, typeCache);
  57. if (marshalType == null)
  58. {
  59. Common.ReportGenericTypeArgumentMustBeVariant(context, typeSyntax, typeSymbol);
  60. continue;
  61. }
  62. }
  63. }
  64. /// <summary>
  65. /// Check if the syntax node is inside a documentation syntax.
  66. /// </summary>
  67. /// <param name="syntax">Syntax node to check.</param>
  68. /// <returns><see langword="true"/> if the syntax node is inside a documentation syntax.</returns>
  69. private bool IsInsideDocumentation(SyntaxNode? syntax)
  70. {
  71. while (syntax != null)
  72. {
  73. if (syntax is DocumentationCommentTriviaSyntax)
  74. {
  75. return true;
  76. }
  77. syntax = syntax.Parent;
  78. }
  79. return false;
  80. }
  81. /// <summary>
  82. /// Check if the given type argument is being used in a type parameter that contains
  83. /// the <c>MustBeVariantAttribute</c>; otherwise, we ignore the attribute.
  84. /// </summary>
  85. /// <param name="context">Context for a syntax node action.</param>
  86. /// <param name="parentSyntax">The parent node syntax that contains the type node syntax.</param>
  87. /// <param name="parentSymbol">The symbol retrieved for the parent node syntax.</param>
  88. /// <param name="typeArgumentSyntax">The type node syntax of the argument type to check.</param>
  89. /// <param name="typeArgumentSymbol">The symbol retrieved for the type node syntax.</param>
  90. /// <returns><see langword="true"/> if the type must be variant and must be analyzed.</returns>
  91. private bool ShouldCheckTypeArgument(SyntaxNodeAnalysisContext context, SyntaxNode parentSyntax, ISymbol parentSymbol, TypeSyntax typeArgumentSyntax, ITypeSymbol typeArgumentSymbol, int typeArgumentIndex)
  92. {
  93. var typeParamSymbol = parentSymbol switch
  94. {
  95. IMethodSymbol methodSymbol => methodSymbol.TypeParameters[typeArgumentIndex],
  96. INamedTypeSymbol typeSymbol => typeSymbol.TypeParameters[typeArgumentIndex],
  97. _ => null,
  98. };
  99. if (typeParamSymbol == null)
  100. {
  101. Common.ReportTypeArgumentParentSymbolUnhandled(context, typeArgumentSyntax, parentSymbol);
  102. return false;
  103. }
  104. return typeParamSymbol.GetAttributes()
  105. .Any(a => a.AttributeClass?.IsGodotMustBeVariantAttribute() ?? false);
  106. }
  107. }
  108. }