< Summary

Information
Class: NexusLabs.Needlr.Generators.ConstructorGuardAnalysisHelper
Assembly: NexusLabs.Needlr.Generators
File(s): /home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/ConstructorGuardAnalysisHelper.cs
Line coverage
88%
Covered lines: 673
Uncovered lines: 87
Coverable lines: 760
Total lines: 1356
Line coverage: 88.5%
Branch coverage
82%
Covered branches: 324
Total branches: 392
Branch coverage: 82.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

File(s)

/home/runner/work/needlr/needlr/src/NexusLabs.Needlr.Generators/ConstructorGuardAnalysisHelper.cs

#LineLine coverage
 1using System.Collections.Generic;
 2using System.Collections.Immutable;
 3using System.Linq;
 4
 5using Microsoft.CodeAnalysis;
 6using Microsoft.CodeAnalysis.CSharp;
 7using Microsoft.CodeAnalysis.CSharp.Syntax;
 8using Microsoft.CodeAnalysis.Diagnostics;
 9
 10using NexusLabs.Needlr.Generators.Models;
 11using NexusLabs.Needlr.Roslyn.Shared;
 12
 13namespace NexusLabs.Needlr.Generators;
 14
 15/// <summary>
 16/// Shared analyzer logic for constructor guards applied to generated-constructor
 17/// fields or generated record-overload properties.
 18/// </summary>
 19internal static class ConstructorGuardAnalysisHelper
 20{
 21    private const string ConstructorGuardDefinitionAttributeName = "ConstructorGuardDefinitionAttribute";
 22    private const string DefaultGuardMethodName = "Validate";
 23    private const int AttributeTargetsField = 0x0100;
 24    private const int AttributeTargetsProperty = 0x0080;
 25
 26    /// <summary>
 27    /// Builds a normalized occurrence for a direct
 28    /// <c>ConstructorGuardAttribute</c>.
 29    /// </summary>
 30    internal static ConstructorGuardOccurrence BuildDirectGuardOccurrence(
 31        ISymbol member,
 32        ITypeSymbol memberType,
 33        string memberKind,
 34        AttributeData attribute,
 35        string? ineligibilityReason)
 36    {
 10237        if (attribute.ConstructorArguments.Length == 0)
 38        {
 039            return CreateOccurrence(
 040                member,
 041                memberType,
 042                memberKind,
 043                attribute,
 044                ConstructorGuardOccurrenceKind.BuiltInNone,
 045                ineligibilityReason,
 046                null,
 047                null,
 048                false,
 049                false);
 50        }
 51
 10252        var first = attribute.ConstructorArguments[0];
 10253        if (first.Kind == TypedConstantKind.Enum && first.Value is int enumValue)
 54        {
 3355            var kind = enumValue == 0
 3356                ? ConstructorGuardOccurrenceKind.BuiltInNone
 3357                : ConstructorGuardOccurrenceKind.BuiltInPositive;
 3358            return CreateOccurrence(
 3359                member,
 3360                memberType,
 3361                memberKind,
 3362                attribute,
 3363                kind,
 3364                ineligibilityReason,
 3365                null,
 3366                null,
 3367                false,
 3368                false);
 69        }
 70
 6971        if (first.Value is ITypeSymbol guardType)
 72        {
 6973            var methodNameExplicit = attribute.ConstructorArguments.Length > 1 &&
 6974                attribute.ConstructorArguments[1].Value is string;
 6975            var methodName = methodNameExplicit
 6976                ? (string)attribute.ConstructorArguments[1].Value!
 6977                : DefaultGuardMethodName;
 6978            return CreateOccurrence(
 6979                member,
 6980                memberType,
 6981                memberKind,
 6982                attribute,
 6983                ConstructorGuardOccurrenceKind.CustomType,
 6984                ineligibilityReason,
 6985                guardType,
 6986                methodName,
 6987                methodNameExplicit,
 6988                false);
 89        }
 90
 091        return CreateOccurrence(
 092            member,
 093            memberType,
 094            memberKind,
 095            attribute,
 096            ConstructorGuardOccurrenceKind.BuiltInNone,
 097            ineligibilityReason,
 098            null,
 099            null,
 0100            false,
 0101            false);
 102    }
 103
 104    /// <summary>
 105    /// Builds a normalized occurrence for a custom guard alias usage.
 106    /// </summary>
 107    internal static ConstructorGuardOccurrence BuildAliasOccurrence(
 108        ISymbol member,
 109        ITypeSymbol memberType,
 110        string memberKind,
 111        AttributeData attribute,
 112        string? ineligibilityReason,
 113        ITypeSymbol? guardType,
 114        string? methodName,
 115        bool methodNameExplicit,
 116        bool guardTypeUsageIsInSourceAlias)
 117    {
 34118        return CreateOccurrence(
 34119            member,
 34120            memberType,
 34121            memberKind,
 34122            attribute,
 34123            ConstructorGuardOccurrenceKind.Alias,
 34124            ineligibilityReason,
 34125            guardType,
 34126            methodName,
 34127            methodNameExplicit,
 34128            guardTypeUsageIsInSourceAlias);
 129    }
 130
 131    /// <summary>
 132    /// Resolves a <c>ConstructorGuardDefinitionAttribute</c> from an application-defined
 133    /// alias attribute type.
 134    /// </summary>
 135    internal static bool TryGetGuardDefinition(
 136        INamedTypeSymbol attributeClass,
 137        out ITypeSymbol? guardType,
 138        out string? methodName,
 139        out bool methodNameExplicit)
 140    {
 506141        foreach (var metaAttribute in attributeClass.GetAttributes())
 142        {
 135143            if (metaAttribute.AttributeClass is not { } metaClass ||
 135144                !GeneratedConstructorEligibility.IsNeedlrGeneratorsAttribute(
 135145                    metaClass,
 135146                    ConstructorGuardDefinitionAttributeName))
 147            {
 148                continue;
 149            }
 150
 34151            if (metaAttribute.ConstructorArguments.Length == 0 ||
 34152                metaAttribute.ConstructorArguments[0].Value is not ITypeSymbol resolvedGuardType)
 153            {
 154                continue;
 155            }
 156
 34157            guardType = resolvedGuardType;
 34158            methodNameExplicit = metaAttribute.ConstructorArguments.Length > 1 &&
 34159                metaAttribute.ConstructorArguments[1].Value is string;
 34160            methodName = methodNameExplicit
 34161                ? (string)metaAttribute.ConstructorArguments[1].Value!
 34162                : DefaultGuardMethodName;
 34163            return true;
 164        }
 165
 101166        guardType = null;
 101167        methodName = null;
 101168        methodNameExplicit = false;
 101169        return false;
 170    }
 171
 172    /// <summary>
 173    /// Reports the built-in or custom guard diagnostics for one positive occurrence.
 174    /// </summary>
 175    internal static void AnalyzePositiveGuardOccurrence(
 176        SyntaxNodeAnalysisContext context,
 177        INamedTypeSymbol containingType,
 178        ConstructorGuardOccurrence occurrence,
 179        Location location)
 180    {
 110181        switch (occurrence.Kind)
 182        {
 183            case ConstructorGuardOccurrenceKind.BuiltInPositive:
 14184                AnalyzeBuiltInGuard(context, occurrence, location);
 14185                break;
 186            case ConstructorGuardOccurrenceKind.CustomType:
 65187                AnalyzeCustomGuard(
 65188                    context,
 65189                    containingType,
 65190                    occurrence,
 65191                    location,
 65192                    occurrence.GuardType,
 65193                    occurrence.MethodName,
 65194                    occurrence.MethodNameExplicit,
 65195                    ImmutableArray<ITypeSymbol>.Empty);
 65196                break;
 197            case ConstructorGuardOccurrenceKind.Alias:
 30198                AnalyzeAliasGuardAtUsage(
 30199                    context,
 30200                    containingType,
 30201                    occurrence,
 30202                    location);
 203                break;
 204        }
 30205    }
 206
 207    /// <summary>
 208    /// Returns whether a positive guard occurrence can be emitted as a valid direct
 209    /// call. Used by generators to fail closed when analyzer diagnostics identify an
 210    /// invalid guard declaration.
 211    /// </summary>
 212    internal static bool IsPositiveGuardOccurrenceValidForGeneration(
 213        Compilation compilation,
 214        INamedTypeSymbol containingType,
 215        ConstructorGuardOccurrence occurrence)
 216    {
 13217        switch (occurrence.Kind)
 218        {
 219            case ConstructorGuardOccurrenceKind.BuiltInPositive:
 5220                if (occurrence.Attribute.ConstructorArguments.Length == 0)
 0221                    return false;
 222
 5223                var constant = occurrence.Attribute.ConstructorArguments[0];
 5224                if (!IsDefinedEnumValue(constant) ||
 5225                    constant.Value is not int rawValue)
 226                {
 0227                    return false;
 228                }
 229
 5230                var kind = (BuiltInConstructorGuardKindMirror)rawValue;
 5231                return kind switch
 5232                {
 5233                    BuiltInConstructorGuardKindMirror.NotNull =>
 3234                        CanBeRuntimeNull(occurrence.MemberType),
 5235                    BuiltInConstructorGuardKindMirror.NotNullOrEmpty or
 5236                    BuiltInConstructorGuardKindMirror.NotNullOrWhiteSpace =>
 2237                        occurrence.MemberType.SpecialType ==
 2238                            SpecialType.System_String,
 0239                    _ => false,
 5240                };
 241            case ConstructorGuardOccurrenceKind.CustomType:
 4242                return IsCustomGuardValidForGeneration(
 4243                    compilation,
 4244                    containingType,
 4245                    occurrence,
 4246                    ImmutableArray<ITypeSymbol>.Empty);
 247            case ConstructorGuardOccurrenceKind.Alias:
 4248                if (!TryGetForwardedArgumentTypes(
 4249                    occurrence.Attribute,
 4250                    out var forwardedArgumentTypes,
 4251                    out _))
 252                {
 0253                    return false;
 254                }
 255
 4256                return IsCustomGuardValidForGeneration(
 4257                    compilation,
 4258                    containingType,
 4259                    occurrence,
 4260                    forwardedArgumentTypes);
 261            default:
 0262                return true;
 263        }
 264    }
 265
 266    /// <summary>
 267    /// Reports NDLRGEN047 when an enum-valued guard argument is undefined.
 268    /// </summary>
 269    internal static bool TryReportUndefinedEnum(
 270        SyntaxNodeAnalysisContext context,
 271        Location location,
 272        TypedConstant constant)
 273    {
 16274        if (constant.Kind != TypedConstantKind.Enum ||
 16275            constant.Value is not int rawValue ||
 16276            constant.Type is not { } enumType)
 277        {
 0278            return false;
 279        }
 280
 16281        if (IsDefinedEnumValue(constant))
 12282            return false;
 283
 4284        context.ReportDiagnostic(Diagnostic.Create(
 4285            DiagnosticDescriptors.InvalidConstructorGuardEnumValue,
 4286            location,
 4287            rawValue,
 4288            enumType.Name));
 4289        return true;
 290    }
 291
 292    /// <summary>
 293    /// Resolves the accessible static guard method compatible with a guarded member and
 294    /// any positional arguments forwarded from an alias usage.
 295    /// </summary>
 296    internal static GuardMethodResolution TryResolveGuardMethod(
 297        Compilation compilation,
 298        INamedTypeSymbol withinType,
 299        ITypeSymbol guardType,
 300        string methodName,
 301        ITypeSymbol? memberType,
 302        string memberKind,
 303        ImmutableArray<ITypeSymbol> forwardedArgumentTypes,
 304        out IMethodSymbol? method,
 305        out string? reason,
 306        out GuardResolutionFailureKind failureKind)
 307    {
 125308        method = null;
 125309        reason = null;
 125310        failureKind = GuardResolutionFailureKind.None;
 311
 125312        var candidates = guardType.GetMembers(methodName)
 125313            .OfType<IMethodSymbol>()
 126314            .Where(candidate => candidate.MethodKind == MethodKind.Ordinary)
 125315            .ToList();
 125316        if (candidates.Count == 0)
 317        {
 4318            reason = $"no method named '{methodName}' was found on '{guardType.ToDisplayString()}'";
 4319            failureKind = GuardResolutionFailureKind.General;
 4320            return GuardMethodResolution.NotFound;
 321        }
 322
 121323        var expectedArity = memberType is null
 121324            ? (int?)null
 121325            : forwardedArgumentTypes.Length + 2;
 121326        var matches = new List<IMethodSymbol>();
 327
 494328        foreach (var candidate in candidates)
 329        {
 126330            if (!compilation.IsSymbolAccessibleWithin(candidate, withinType))
 331            {
 4332                reason = "it is not accessible";
 4333                failureKind = GuardResolutionFailureKind.General;
 4334                continue;
 335            }
 336
 122337            if (!candidate.IsStatic)
 338            {
 2339                reason = "it is not static";
 2340                failureKind = GuardResolutionFailureKind.General;
 2341                continue;
 342            }
 343
 120344            if (!candidate.ReturnsVoid)
 345            {
 2346                reason = "it does not return void";
 2347                failureKind = GuardResolutionFailureKind.General;
 2348                continue;
 349            }
 350
 118351            if (candidate.Parameters.Length < 2)
 352            {
 2353                reason = "it does not have at least a value parameter and a trailing string parameter name";
 2354                failureKind = GuardResolutionFailureKind.General;
 2355                continue;
 356            }
 357
 116358            if (expectedArity.HasValue &&
 116359                candidate.Parameters.Length != expectedArity.Value)
 360            {
 2361                reason = $"it has {candidate.Parameters.Length - 2} parameter(s) between the value and the parameter nam
 2362                failureKind = GuardResolutionFailureKind.ForwardedArgument;
 2363                continue;
 364            }
 365
 114366            if (candidate.Parameters[candidate.Parameters.Length - 1].Type.SpecialType !=
 114367                SpecialType.System_String)
 368            {
 0369                reason = "its last parameter is not a string parameter name";
 0370                failureKind = GuardResolutionFailureKind.General;
 0371                continue;
 372            }
 373
 114374            var refKindParameter = candidate.Parameters.FirstOrDefault(
 390375                parameter => parameter.RefKind != RefKind.None);
 114376            if (refKindParameter is not null)
 377            {
 6378                reason = $"its '{refKindParameter.Name}' parameter is passed by '{refKindParameter.RefKind.ToString().To
 6379                failureKind = GuardResolutionFailureKind.General;
 6380                continue;
 381            }
 382
 108383            if (memberType is null)
 384            {
 31385                matches.Add(candidate);
 31386                continue;
 387            }
 388
 77389            var valueParameterType = candidate.Parameters[0].Type;
 77390            var middleParameters = candidate.Parameters
 77391                .Skip(1)
 77392                .Take(candidate.Parameters.Length - 2)
 77393                .ToList();
 394
 395            bool isCompatible;
 396            string? candidateReason;
 397            GuardResolutionFailureKind candidateFailureKind;
 398
 77399            if (candidate.IsGenericMethod)
 400            {
 43401                isCompatible = TryInferGenericParameterCompatibility(
 43402                    candidate,
 43403                    valueParameterType,
 43404                    memberType,
 43405                    memberKind,
 43406                    middleParameters,
 43407                    forwardedArgumentTypes,
 43408                    compilation,
 43409                    out candidateReason,
 43410                    out candidateFailureKind);
 411            }
 412            else
 413            {
 34414                isCompatible = TryCheckNonGenericCompatibility(
 34415                    memberType,
 34416                    memberKind,
 34417                    valueParameterType,
 34418                    middleParameters,
 34419                    forwardedArgumentTypes,
 34420                    compilation,
 34421                    out candidateReason,
 34422                    out candidateFailureKind);
 423            }
 424
 77425            if (!isCompatible)
 426            {
 36427                reason = candidateReason;
 36428                failureKind = candidateFailureKind;
 36429                continue;
 430            }
 431
 41432            matches.Add(candidate);
 433        }
 434
 121435        if (matches.Count == 1)
 436        {
 62437            method = matches[0];
 62438            failureKind = GuardResolutionFailureKind.None;
 62439            return GuardMethodResolution.Found;
 440        }
 441
 59442        if (matches.Count > 1 && memberType is not null)
 443        {
 4444            matches = matches
 8445                .Where(candidate => !matches.Any(other =>
 22446                    !SymbolEqualityComparer.Default.Equals(candidate, other) &&
 22447                    IsBetterGuardMethod(
 22448                        other,
 22449                        candidate,
 22450                        memberType,
 22451                        forwardedArgumentTypes,
 22452                        compilation)))
 4453                .ToList();
 454        }
 455
 59456        if (matches.Count == 1)
 457        {
 2458            method = matches[0];
 2459            failureKind = GuardResolutionFailureKind.None;
 2460            return GuardMethodResolution.Found;
 461        }
 462
 57463        if (matches.Count > 1)
 464        {
 3465            failureKind = GuardResolutionFailureKind.None;
 3466            return GuardMethodResolution.Ambiguous;
 467        }
 468
 54469        if (reason is null)
 470        {
 0471            reason = "no accessible static method compatible with (value, ...forwarded arguments, string parameterName) 
 0472            failureKind = GuardResolutionFailureKind.General;
 473        }
 474
 54475        return GuardMethodResolution.NotFound;
 476    }
 477
 478    private static bool IsBetterGuardMethod(
 479        IMethodSymbol candidate,
 480        IMethodSymbol other,
 481        ITypeSymbol memberType,
 482        ImmutableArray<ITypeSymbol> forwardedArgumentTypes,
 483        Compilation compilation)
 484    {
 8485        var argumentTypes = forwardedArgumentTypes.Insert(0, memberType);
 8486        var candidateIsBetter = false;
 8487        var otherIsBetter = false;
 488
 36489        for (var i = 0; i < argumentTypes.Length; i++)
 490        {
 10491            var argumentType = argumentTypes[i];
 10492            var candidateType = candidate.Parameters[i].Type;
 10493            var otherType = other.Parameters[i].Type;
 10494            if (SymbolEqualityComparer.Default.Equals(candidateType, otherType))
 495                continue;
 496
 8497            var candidateIdentity = SymbolEqualityComparer.Default.Equals(
 8498                argumentType,
 8499                candidateType);
 8500            var otherIdentity = SymbolEqualityComparer.Default.Equals(
 8501                argumentType,
 8502                otherType);
 8503            if (candidateIdentity != otherIdentity)
 504            {
 4505                candidateIsBetter |= candidateIdentity;
 4506                otherIsBetter |= otherIdentity;
 4507                continue;
 508            }
 509
 4510            var candidateToOther = IsAssignableTo(
 4511                candidateType,
 4512                otherType,
 4513                compilation);
 4514            var otherToCandidate = IsAssignableTo(
 4515                otherType,
 4516                candidateType,
 4517                compilation);
 4518            candidateIsBetter |= candidateToOther && !otherToCandidate;
 4519            otherIsBetter |= otherToCandidate && !candidateToOther;
 520        }
 521
 8522        if (candidateIsBetter != otherIsBetter)
 4523            return candidateIsBetter;
 524
 4525        return !candidateIsBetter &&
 4526            !candidate.IsGenericMethod &&
 4527            other.IsGenericMethod;
 528    }
 529
 530    /// <summary>
 531    /// Returns why an alias definition cannot target constructor-guard members.
 532    /// </summary>
 533    internal static string? GetGuardDefinitionTargetInvalidReason(
 534        INamedTypeSymbol attributeClass)
 535    {
 38536        if (!InheritsFromSystemAttribute(attributeClass))
 2537            return "not derived from System.Attribute";
 538
 36539        var usageAttribute = attributeClass.GetAttributes()
 36540            .FirstOrDefault(attribute =>
 108541                attribute.AttributeClass?.ToDisplayString() ==
 108542                "System.AttributeUsageAttribute");
 36543        if (usageAttribute is not null &&
 36544            usageAttribute.ConstructorArguments.Length > 0 &&
 36545            usageAttribute.ConstructorArguments[0].Value is int validOn &&
 36546            (validOn & (AttributeTargetsField | AttributeTargetsProperty)) == 0)
 547        {
 2548            return "not usable on fields or properties ([AttributeUsage] includes neither AttributeTargets.Field nor Att
 549        }
 550
 34551        return null;
 552    }
 553
 554    /// <summary>
 555    /// Gets the source location of an attribute occurrence.
 556    /// </summary>
 557    internal static Location GetAttributeLocation(
 558        SyntaxNodeAnalysisContext context,
 559        AttributeData attribute)
 560    {
 192561        return attribute.ApplicationSyntaxReference?
 192562            .GetSyntax(context.CancellationToken)
 192563            .GetLocation() ?? Location.None;
 564    }
 565
 566    private static ConstructorGuardOccurrence CreateOccurrence(
 567        ISymbol member,
 568        ITypeSymbol memberType,
 569        string memberKind,
 570        AttributeData attribute,
 571        ConstructorGuardOccurrenceKind kind,
 572        string? ineligibilityReason,
 573        ITypeSymbol? guardType,
 574        string? methodName,
 575        bool methodNameExplicit,
 576        bool guardTypeUsageIsInSourceAlias)
 577    {
 136578        return new ConstructorGuardOccurrence(
 136579            member,
 136580            memberType,
 136581            memberKind,
 136582            attribute,
 136583            kind,
 136584            ineligibilityReason,
 136585            guardType,
 136586            methodName,
 136587            methodNameExplicit,
 136588            guardTypeUsageIsInSourceAlias);
 589    }
 590
 591    private static bool IsCustomGuardValidForGeneration(
 592        Compilation compilation,
 593        INamedTypeSymbol containingType,
 594        ConstructorGuardOccurrence occurrence,
 595        ImmutableArray<ITypeSymbol> forwardedArgumentTypes)
 596    {
 8597        if (occurrence.GuardType is null ||
 8598            occurrence.GuardType.TypeKind == TypeKind.Error ||
 8599            !compilation.IsSymbolAccessibleWithin(
 8600                occurrence.GuardType,
 8601                containingType) ||
 8602            occurrence.MethodNameExplicit &&
 8603                string.IsNullOrWhiteSpace(occurrence.MethodName))
 604        {
 0605            return false;
 606        }
 607
 8608        var methodName = string.IsNullOrEmpty(occurrence.MethodName)
 8609            ? DefaultGuardMethodName
 8610            : occurrence.MethodName!;
 8611        return TryResolveGuardMethod(
 8612            compilation,
 8613            containingType,
 8614            occurrence.GuardType,
 8615            methodName,
 8616            occurrence.MemberType,
 8617            occurrence.MemberKind,
 8618            forwardedArgumentTypes,
 8619            out _,
 8620            out _,
 8621            out _) == GuardMethodResolution.Found;
 622    }
 623
 624    private static bool IsDefinedEnumValue(TypedConstant constant)
 625    {
 21626        if (constant.Kind != TypedConstantKind.Enum ||
 21627            constant.Value is not int rawValue ||
 21628            constant.Type is not { } enumType)
 629        {
 0630            return false;
 631        }
 632
 21633        return enumType.GetMembers()
 21634            .OfType<IFieldSymbol>()
 21635            .Any(field =>
 80636                field.HasConstantValue &&
 80637                field.ConstantValue is int memberValue &&
 80638                memberValue == rawValue);
 639    }
 640
 641    private static void AnalyzeAliasGuardAtUsage(
 642        SyntaxNodeAnalysisContext context,
 643        INamedTypeSymbol containingType,
 644        ConstructorGuardOccurrence occurrence,
 645        Location location)
 646    {
 30647        if (!TryGetForwardedArgumentTypes(
 30648            occurrence.Attribute,
 30649            out var forwardedArgumentTypes,
 30650            out var unsupportedReason))
 651        {
 6652            context.ReportDiagnostic(Diagnostic.Create(
 6653                DiagnosticDescriptors.ConstructorGuardAliasUsageArgumentUnsupported,
 6654                location,
 6655                occurrence.Member.Name,
 6656                unsupportedReason));
 6657            return;
 658        }
 659
 24660        if (!occurrence.GuardTypeUsageIsInSourceAlias)
 661        {
 0662            AnalyzeCustomGuard(
 0663                context,
 0664                containingType,
 0665                occurrence,
 0666                location,
 0667                occurrence.GuardType,
 0668                occurrence.MethodName,
 0669                occurrence.MethodNameExplicit,
 0670                forwardedArgumentTypes);
 0671            return;
 672        }
 673
 24674        if (occurrence.GuardType is null ||
 24675            occurrence.GuardType.TypeKind == TypeKind.Error)
 676        {
 0677            return;
 678        }
 679
 24680        var methodName = string.IsNullOrEmpty(occurrence.MethodName)
 24681            ? DefaultGuardMethodName
 24682            : occurrence.MethodName!;
 24683        var resolution = TryResolveGuardMethod(
 24684            context.Compilation,
 24685            containingType,
 24686            occurrence.GuardType,
 24687            methodName,
 24688            occurrence.MemberType,
 24689            occurrence.MemberKind,
 24690            forwardedArgumentTypes,
 24691            out _,
 24692            out var reason,
 24693            out var failureKind);
 694
 24695        if (resolution == GuardMethodResolution.NotFound &&
 24696            failureKind == GuardResolutionFailureKind.ForwardedArgument)
 697        {
 10698            context.ReportDiagnostic(Diagnostic.Create(
 10699                DiagnosticDescriptors.ConstructorGuardForwardedArgumentIncompatible,
 10700                location,
 10701                methodName,
 10702                occurrence.GuardType.ToDisplayString(),
 10703                occurrence.Member.Name,
 10704                occurrence.MemberType.ToDisplayString(),
 10705                reason));
 706        }
 14707        else if (resolution == GuardMethodResolution.NotFound &&
 14708            reason == GetMemberTypeIncompatibleReason(occurrence.MemberKind))
 709        {
 2710            context.ReportDiagnostic(Diagnostic.Create(
 2711                DiagnosticDescriptors.ConstructorGuardMethodInvalid,
 2712                location,
 2713                methodName,
 2714                occurrence.GuardType.ToDisplayString(),
 2715                occurrence.Member.Name,
 2716                occurrence.MemberType.ToDisplayString(),
 2717                reason));
 718        }
 12719        else if (resolution == GuardMethodResolution.Ambiguous)
 720        {
 0721            context.ReportDiagnostic(Diagnostic.Create(
 0722                DiagnosticDescriptors.ConstructorGuardMethodAmbiguous,
 0723                location,
 0724                methodName,
 0725                occurrence.GuardType.ToDisplayString(),
 0726                occurrence.Member.Name,
 0727                occurrence.MemberType.ToDisplayString()));
 728        }
 12729    }
 730
 731    private static void AnalyzeBuiltInGuard(
 732        SyntaxNodeAnalysisContext context,
 733        ConstructorGuardOccurrence occurrence,
 734        Location location)
 735    {
 14736        var first = occurrence.Attribute.ConstructorArguments[0];
 14737        if (TryReportUndefinedEnum(context, location, first))
 2738            return;
 739
 12740        if (first.Value is not int rawValue)
 0741            return;
 742
 12743        var kind = (BuiltInConstructorGuardKindMirror)rawValue;
 744        switch (kind)
 745        {
 746            case BuiltInConstructorGuardKindMirror.NotNull:
 7747                if (!CanBeRuntimeNull(occurrence.MemberType))
 748                {
 4749                    context.ReportDiagnostic(Diagnostic.Create(
 4750                        DiagnosticDescriptors.ConstructorGuardIncompatibleWithFieldType,
 4751                        location,
 4752                        "NotNull",
 4753                        occurrence.Member.Name,
 4754                        occurrence.MemberType.ToDisplayString(),
 4755                        $"the {occurrence.MemberKind}'s type is a non-nullable value type, so a runtime null value is ne
 756                }
 757
 4758                break;
 759            case BuiltInConstructorGuardKindMirror.NotNullOrEmpty:
 760            case BuiltInConstructorGuardKindMirror.NotNullOrWhiteSpace:
 5761                if (occurrence.MemberType.SpecialType != SpecialType.System_String)
 762                {
 4763                    context.ReportDiagnostic(Diagnostic.Create(
 4764                        DiagnosticDescriptors.ConstructorGuardIncompatibleWithFieldType,
 4765                        location,
 4766                        kind.ToString(),
 4767                        occurrence.Member.Name,
 4768                        occurrence.MemberType.ToDisplayString(),
 4769                        $"this guard only applies to string-compatible {GetMemberKindPlural(occurrence.MemberKind)}"));
 770                }
 771
 772                break;
 773        }
 8774    }
 775
 776    private static void AnalyzeCustomGuard(
 777        SyntaxNodeAnalysisContext context,
 778        INamedTypeSymbol containingType,
 779        ConstructorGuardOccurrence occurrence,
 780        Location location,
 781        ITypeSymbol? guardType,
 782        string? methodName,
 783        bool methodNameExplicit,
 784        ImmutableArray<ITypeSymbol> forwardedArgumentTypes)
 785    {
 65786        if (guardType is null || guardType.TypeKind == TypeKind.Error)
 787        {
 2788            context.ReportDiagnostic(Diagnostic.Create(
 2789                DiagnosticDescriptors.ConstructorGuardTypeInvalid,
 2790                location,
 2791                occurrence.Member.Name,
 2792                "the guard type could not be resolved"));
 2793            return;
 794        }
 795
 63796        if (!context.Compilation.IsSymbolAccessibleWithin(guardType, containingType))
 797        {
 0798            context.ReportDiagnostic(Diagnostic.Create(
 0799                DiagnosticDescriptors.ConstructorGuardTypeInvalid,
 0800                location,
 0801                occurrence.Member.Name,
 0802                $"'{guardType.ToDisplayString()}' is not accessible from '{containingType.ToDisplayString()}'"));
 0803            return;
 804        }
 805
 63806        if (methodNameExplicit && string.IsNullOrWhiteSpace(methodName))
 807        {
 2808            context.ReportDiagnostic(Diagnostic.Create(
 2809                DiagnosticDescriptors.ConstructorGuardMethodNameInvalid,
 2810                location,
 2811                occurrence.Member.Name));
 2812            return;
 813        }
 814
 61815        var effectiveMethodName = string.IsNullOrEmpty(methodName)
 61816            ? DefaultGuardMethodName
 61817            : methodName!;
 61818        var resolution = TryResolveGuardMethod(
 61819            context.Compilation,
 61820            containingType,
 61821            guardType,
 61822            effectiveMethodName,
 61823            occurrence.MemberType,
 61824            occurrence.MemberKind,
 61825            forwardedArgumentTypes,
 61826            out _,
 61827            out var reason,
 61828            out var failureKind);
 829
 61830        switch (resolution)
 831        {
 832            case GuardMethodResolution.NotFound
 44833                when failureKind == GuardResolutionFailureKind.ForwardedArgument:
 0834                context.ReportDiagnostic(Diagnostic.Create(
 0835                    DiagnosticDescriptors.ConstructorGuardForwardedArgumentIncompatible,
 0836                    location,
 0837                    effectiveMethodName,
 0838                    guardType.ToDisplayString(),
 0839                    occurrence.Member.Name,
 0840                    occurrence.MemberType.ToDisplayString(),
 0841                    reason));
 0842                break;
 843            case GuardMethodResolution.NotFound:
 44844                context.ReportDiagnostic(Diagnostic.Create(
 44845                    DiagnosticDescriptors.ConstructorGuardMethodInvalid,
 44846                    location,
 44847                    effectiveMethodName,
 44848                    guardType.ToDisplayString(),
 44849                    occurrence.Member.Name,
 44850                    occurrence.MemberType.ToDisplayString(),
 44851                    reason ?? "no compatible method was found"));
 44852                break;
 853            case GuardMethodResolution.Ambiguous:
 2854                context.ReportDiagnostic(Diagnostic.Create(
 2855                    DiagnosticDescriptors.ConstructorGuardMethodAmbiguous,
 2856                    location,
 2857                    effectiveMethodName,
 2858                    guardType.ToDisplayString(),
 2859                    occurrence.Member.Name,
 2860                    occurrence.MemberType.ToDisplayString()));
 861                break;
 862        }
 2863    }
 864
 865    private static bool TryGetForwardedArgumentTypes(
 866        AttributeData attribute,
 867        out ImmutableArray<ITypeSymbol> forwardedArgumentTypes,
 868        out string? unsupportedReason)
 869    {
 34870        if (attribute.NamedArguments.Length > 0)
 871        {
 2872            forwardedArgumentTypes = ImmutableArray<ITypeSymbol>.Empty;
 2873            unsupportedReason = $"named argument '{attribute.NamedArguments[0].Key}' is not forwarded to the guard metho
 2874            return false;
 875        }
 876
 32877        if (attribute.ConstructorArguments.Length == 0)
 878        {
 3879            forwardedArgumentTypes = ImmutableArray<ITypeSymbol>.Empty;
 3880            unsupportedReason = null;
 3881            return true;
 882        }
 883
 29884        var builder = ImmutableArray.CreateBuilder<ITypeSymbol>(
 29885            attribute.ConstructorArguments.Length);
 114886        for (var i = 0; i < attribute.ConstructorArguments.Length; i++)
 887        {
 32888            var constant = attribute.ConstructorArguments[i];
 32889            if (!TypedConstantRenderer.TryRender(constant, out _))
 890            {
 4891                forwardedArgumentTypes = ImmutableArray<ITypeSymbol>.Empty;
 4892                unsupportedReason = $"positional argument {i + 1} is {DescribeUnsupportedConstant(constant)}, which is n
 4893                return false;
 894            }
 895
 28896            builder.Add(constant.Type!);
 897        }
 898
 25899        forwardedArgumentTypes = builder.MoveToImmutable();
 25900        unsupportedReason = null;
 25901        return true;
 902    }
 903
 904    private static string DescribeUnsupportedConstant(TypedConstant constant)
 905    {
 4906        if (constant.Kind == TypedConstantKind.Array)
 2907            return "an array";
 908
 2909        if (constant.Value is float or double)
 2910            return "a floating-point value";
 911
 0912        return "an unsupported value";
 913    }
 914
 915    internal static bool CanBeRuntimeNull(ITypeSymbol type)
 916    {
 23917        if (type.IsReferenceType)
 16918            return true;
 919
 7920        if (type is ITypeParameterSymbol typeParameter)
 921        {
 3922            return !typeParameter.HasValueTypeConstraint &&
 3923                !typeParameter.HasUnmanagedTypeConstraint;
 924        }
 925
 4926        return type is INamedTypeSymbol
 4927        {
 4928            OriginalDefinition.SpecialType: SpecialType.System_Nullable_T,
 4929        };
 930    }
 931
 932    private static bool IsAssignableTo(
 933        ITypeSymbol sourceType,
 934        ITypeSymbol parameterType,
 935        Compilation compilation)
 936    {
 65937        if (SymbolEqualityComparer.Default.Equals(sourceType, parameterType))
 39938            return true;
 939
 26940        var conversion = compilation.ClassifyConversion(sourceType, parameterType);
 26941        return conversion.Exists && (conversion.IsIdentity || conversion.IsImplicit);
 942    }
 943
 944    private static bool TryCheckNonGenericCompatibility(
 945        ITypeSymbol memberType,
 946        string memberKind,
 947        ITypeSymbol valueParameterType,
 948        List<IParameterSymbol> middleParameters,
 949        ImmutableArray<ITypeSymbol> forwardedArgumentTypes,
 950        Compilation compilation,
 951        out string? reason,
 952        out GuardResolutionFailureKind failureKind)
 953    {
 34954        if (!IsAssignableTo(memberType, valueParameterType, compilation))
 955        {
 4956            reason = GetMemberTypeIncompatibleReason(memberKind);
 4957            failureKind = GuardResolutionFailureKind.General;
 4958            return false;
 959        }
 960
 88961        for (var i = 0; i < middleParameters.Count; i++)
 962        {
 18963            if (IsAssignableTo(
 18964                forwardedArgumentTypes[i],
 18965                middleParameters[i].Type,
 18966                compilation))
 967            {
 968                continue;
 969            }
 970
 4971            reason = $"its parameter '{middleParameters[i].Name}' of type '{middleParameters[i].Type.ToDisplayString()}'
 4972            failureKind = GuardResolutionFailureKind.ForwardedArgument;
 4973            return false;
 974        }
 975
 26976        reason = null;
 26977        failureKind = GuardResolutionFailureKind.None;
 26978        return true;
 979    }
 980
 981    private static bool TryInferGenericParameterCompatibility(
 982        IMethodSymbol genericMethod,
 983        ITypeSymbol parameterType,
 984        ITypeSymbol memberType,
 985        string memberKind,
 986        List<IParameterSymbol> middleParameters,
 987        ImmutableArray<ITypeSymbol> forwardedArgumentTypes,
 988        Compilation compilation,
 989        out string? reason,
 990        out GuardResolutionFailureKind failureKind)
 991    {
 43992        var methodTypeParameters = new HashSet<ITypeSymbol>(
 43993            genericMethod.TypeParameters,
 43994            SymbolEqualityComparer.Default);
 43995        string? lastReason = null;
 43996        var lastFailureKind = GuardResolutionFailureKind.General;
 997
 43998        var candidateTypes = parameterType is ITypeParameterSymbol typeParameter &&
 43999            methodTypeParameters.Contains(typeParameter)
 431000                ? new[] { memberType }
 431001                : GetTypeAndSupertypes(memberType);
 1811002        foreach (var candidateType in candidateTypes)
 1003        {
 551004            var substitution = new Dictionary<ITypeSymbol, ITypeSymbol>(
 551005                SymbolEqualityComparer.Default);
 551006            if (!TryUnify(
 551007                parameterType,
 551008                candidateType,
 551009                methodTypeParameters,
 551010                substitution,
 551011                compilation,
 551012                allowImplicitTypeParameterConversion:
 551013                    parameterType is ITypeParameterSymbol))
 1014            {
 1015                continue;
 1016            }
 1017
 411018            var forwardedMismatch = false;
 921019            for (var i = 0; i < middleParameters.Count; i++)
 1020            {
 71021                if (TryUnify(
 71022                    middleParameters[i].Type,
 71023                    forwardedArgumentTypes[i],
 71024                    methodTypeParameters,
 71025                    substitution,
 71026                    compilation,
 71027                    allowImplicitTypeParameterConversion:
 71028                        middleParameters[i].Type is ITypeParameterSymbol))
 1029                {
 1030                    continue;
 1031                }
 1032
 21033                lastReason ??= $"its parameter '{middleParameters[i].Name}' cannot accept the forwarded argument of type
 21034                lastFailureKind = GuardResolutionFailureKind.ForwardedArgument;
 21035                forwardedMismatch = true;
 21036                break;
 1037            }
 1038
 411039            if (forwardedMismatch)
 1040                continue;
 1041
 391042            var unboundParameter = genericMethod.TypeParameters.FirstOrDefault(
 821043                typeParameter => !substitution.ContainsKey(typeParameter));
 391044            if (unboundParameter is not null)
 1045            {
 21046                var onlyForwarded = IsReachableOnlyThroughForwardedParameters(
 21047                    unboundParameter,
 21048                    parameterType,
 21049                    middleParameters);
 21050                lastReason ??= onlyForwarded
 21051                    ? $"its type parameter '{unboundParameter.Name}' cannot be inferred from the {memberKind}'s type or 
 21052                    : $"its type parameter '{unboundParameter.Name}' cannot be inferred from the {memberKind}'s type";
 21053                lastFailureKind = onlyForwarded
 21054                    ? GuardResolutionFailureKind.ForwardedArgument
 21055                    : GuardResolutionFailureKind.General;
 21056                continue;
 1057            }
 1058
 371059            var constraintViolation = FindConstraintViolation(
 371060                genericMethod.TypeParameters,
 371061                substitution,
 371062                compilation,
 371063                out var violatingTypeParameter);
 371064            if (constraintViolation is not null)
 1065            {
 221066                var onlyForwarded = violatingTypeParameter is not null &&
 221067                    IsReachableOnlyThroughForwardedParameters(
 221068                        violatingTypeParameter,
 221069                        parameterType,
 221070                        middleParameters);
 221071                lastReason ??= constraintViolation;
 221072                lastFailureKind = onlyForwarded
 221073                    ? GuardResolutionFailureKind.ForwardedArgument
 221074                    : GuardResolutionFailureKind.General;
 221075                continue;
 1076            }
 1077
 151078            reason = null;
 151079            failureKind = GuardResolutionFailureKind.None;
 151080            return true;
 1081        }
 1082
 281083        reason = lastReason ?? GetMemberTypeIncompatibleReason(memberKind);
 281084        failureKind = lastReason is null
 281085            ? GuardResolutionFailureKind.General
 281086            : lastFailureKind;
 281087        return false;
 151088    }
 1089
 1090    private static string GetMemberTypeIncompatibleReason(string memberKind)
 1091    {
 81092        return $"its value parameter type is not compatible with the {memberKind}'s type";
 1093    }
 1094
 1095    private static string GetMemberKindPlural(string memberKind)
 1096    {
 41097        return memberKind == "property" ? "properties" : memberKind + "s";
 1098    }
 1099
 1100    private static bool ContainsTypeParameter(
 1101        ITypeSymbol type,
 1102        ITypeParameterSymbol typeParameter)
 1103    {
 261104        if (SymbolEqualityComparer.Default.Equals(type, typeParameter))
 221105            return true;
 1106
 41107        if (type is INamedTypeSymbol namedType)
 1108        {
 01109            return namedType.TypeArguments.Any(
 01110                typeArgument => ContainsTypeParameter(typeArgument, typeParameter));
 1111        }
 1112
 41113        if (type is IArrayTypeSymbol arrayType)
 01114            return ContainsTypeParameter(arrayType.ElementType, typeParameter);
 1115
 41116        return false;
 1117    }
 1118
 1119    private static bool IsReachableOnlyThroughForwardedParameters(
 1120        ITypeParameterSymbol typeParameter,
 1121        ITypeSymbol valueParameterType,
 1122        List<IParameterSymbol> middleParameters)
 1123    {
 241124        if (ContainsTypeParameter(valueParameterType, typeParameter))
 201125            return false;
 1126
 41127        return middleParameters.Any(
 61128            parameter => ContainsTypeParameter(parameter.Type, typeParameter));
 1129    }
 1130
 1131    private static string? FindConstraintViolation(
 1132        ImmutableArray<ITypeParameterSymbol> typeParameters,
 1133        Dictionary<ITypeSymbol, ITypeSymbol> substitution,
 1134        Compilation compilation,
 1135        out ITypeParameterSymbol? violatingTypeParameter)
 1136    {
 1301137        foreach (var typeParameter in typeParameters)
 1138        {
 391139            if (!substitution.TryGetValue(typeParameter, out var argumentType))
 1140                continue;
 1141
 391142            if (typeParameter.HasReferenceTypeConstraint &&
 391143                !argumentType.IsReferenceType)
 1144            {
 01145                violatingTypeParameter = typeParameter;
 01146                return $"its type parameter '{typeParameter.Name}' requires a reference type, but '{argumentType.ToDispl
 1147            }
 1148
 391149            if (typeParameter.HasReferenceTypeConstraint &&
 391150                typeParameter.ReferenceTypeConstraintNullableAnnotation !=
 391151                    NullableAnnotation.Annotated &&
 391152                argumentType.NullableAnnotation == NullableAnnotation.Annotated)
 1153            {
 21154                violatingTypeParameter = typeParameter;
 21155                return $"its type parameter '{typeParameter.Name}' requires a non-nullable reference type, but '{argumen
 1156            }
 1157
 371158            if (typeParameter.HasValueTypeConstraint &&
 371159                (!argumentType.IsValueType ||
 371160                    IsNullableValueType(argumentType)))
 1161            {
 21162                violatingTypeParameter = typeParameter;
 21163                return $"its type parameter '{typeParameter.Name}' requires a non-nullable value type, but '{argumentTyp
 1164            }
 1165
 351166            if (typeParameter.HasUnmanagedTypeConstraint &&
 351167                !argumentType.IsUnmanagedType)
 1168            {
 21169                violatingTypeParameter = typeParameter;
 21170                return $"its type parameter '{typeParameter.Name}' requires an unmanaged type, but '{argumentType.ToDisp
 1171            }
 1172
 331173            if (typeParameter.HasNotNullConstraint &&
 331174                (argumentType.NullableAnnotation == NullableAnnotation.Annotated ||
 331175                    IsNullableValueType(argumentType)))
 1176            {
 41177                violatingTypeParameter = typeParameter;
 41178                return $"its type parameter '{typeParameter.Name}' requires a non-nullable type, but '{argumentType.ToDi
 1179            }
 1180
 291181            if (typeParameter.HasConstructorConstraint &&
 291182                !SatisfiesConstructorConstraint(argumentType))
 1183            {
 61184                violatingTypeParameter = typeParameter;
 61185                return $"its type parameter '{typeParameter.Name}' requires a non-abstract type with a public parameterl
 1186            }
 1187
 561188            foreach (var constraintType in typeParameter.ConstraintTypes)
 1189            {
 81190                if (SymbolEqualityComparer.Default.Equals(
 81191                    argumentType,
 81192                    constraintType))
 1193                {
 1194                    continue;
 1195                }
 1196
 81197                var conversion = compilation.ClassifyConversion(
 81198                    argumentType,
 81199                    constraintType);
 81200                if (conversion.Exists &&
 81201                    (conversion.IsIdentity || conversion.IsImplicit))
 1202                {
 1203                    continue;
 1204                }
 1205
 61206                violatingTypeParameter = typeParameter;
 61207                return $"its type parameter '{typeParameter.Name}' requires '{constraintType.ToDisplayString()}', which 
 1208            }
 1209        }
 1210
 151211        violatingTypeParameter = null;
 151212        return null;
 1213    }
 1214
 1215    private static IEnumerable<ITypeSymbol> GetTypeAndSupertypes(ITypeSymbol type)
 1216    {
 51217        yield return type;
 1218
 21219        var current = (type as INamedTypeSymbol)?.BaseType;
 21220        while (current is not null)
 1221        {
 01222            yield return current;
 01223            current = current.BaseType;
 1224        }
 1225
 281226        foreach (var iface in type.AllInterfaces)
 1227        {
 121228            yield return iface;
 1229        }
 21230    }
 1231
 1232    private static bool TryUnify(
 1233        ITypeSymbol parameterType,
 1234        ITypeSymbol candidateType,
 1235        HashSet<ITypeSymbol> methodTypeParameters,
 1236        Dictionary<ITypeSymbol, ITypeSymbol> substitution,
 1237        Compilation compilation,
 1238        bool allowImplicitTypeParameterConversion)
 1239    {
 651240        if (parameterType is ITypeParameterSymbol typeParameter &&
 651241            methodTypeParameters.Contains(typeParameter))
 1242        {
 471243            if (substitution.TryGetValue(typeParameter, out var bound))
 1244            {
 41245                if (SymbolEqualityComparer.Default.Equals(bound, candidateType))
 11246                    return true;
 1247
 31248                if (!allowImplicitTypeParameterConversion)
 01249                    return false;
 1250
 31251                if (IsAssignableTo(candidateType, bound, compilation))
 11252                    return true;
 1253
 21254                if (!IsAssignableTo(bound, candidateType, compilation))
 21255                    return false;
 1256
 01257                substitution[typeParameter] = candidateType;
 01258                return true;
 1259            }
 1260
 431261            substitution[typeParameter] = candidateType;
 431262            return true;
 1263        }
 1264
 181265        if (parameterType is INamedTypeSymbol namedParameter &&
 181266            candidateType is INamedTypeSymbol namedCandidate)
 1267        {
 31268            if (!SymbolEqualityComparer.Default.Equals(
 31269                namedParameter.OriginalDefinition,
 31270                namedCandidate.OriginalDefinition))
 1271            {
 01272                return false;
 1273            }
 1274
 31275            if (namedParameter.TypeArguments.Length !=
 31276                namedCandidate.TypeArguments.Length)
 1277            {
 01278                return false;
 1279            }
 1280
 101281            for (var i = 0; i < namedParameter.TypeArguments.Length; i++)
 1282            {
 21283                if (!TryUnify(
 21284                    namedParameter.TypeArguments[i],
 21285                    namedCandidate.TypeArguments[i],
 21286                    methodTypeParameters,
 21287                    substitution,
 21288                    compilation,
 21289                    allowImplicitTypeParameterConversion: false))
 1290                {
 01291                    return false;
 1292                }
 1293            }
 1294
 31295            return true;
 1296        }
 1297
 151298        if (parameterType is IArrayTypeSymbol arrayParameter &&
 151299            candidateType is IArrayTypeSymbol arrayCandidate)
 1300        {
 31301            if (arrayParameter.Rank != arrayCandidate.Rank)
 21302                return false;
 1303
 11304            return TryUnify(
 11305                arrayParameter.ElementType,
 11306                arrayCandidate.ElementType,
 11307                methodTypeParameters,
 11308                substitution,
 11309                compilation,
 11310                allowImplicitTypeParameterConversion: false);
 1311        }
 1312
 121313        return SymbolEqualityComparer.Default.Equals(parameterType, candidateType);
 1314    }
 1315
 1316    private static bool IsNullableValueType(ITypeSymbol type)
 1317    {
 71318        return type is INamedTypeSymbol
 71319        {
 71320            OriginalDefinition.SpecialType: SpecialType.System_Nullable_T,
 71321        };
 1322    }
 1323
 1324    private static bool SatisfiesConstructorConstraint(ITypeSymbol type)
 1325    {
 71326        if (type.IsValueType)
 01327            return true;
 1328
 71329        if (type is ITypeParameterSymbol typeParameter)
 1330        {
 01331            return typeParameter.HasConstructorConstraint ||
 01332                typeParameter.HasValueTypeConstraint ||
 01333                typeParameter.HasUnmanagedTypeConstraint;
 1334        }
 1335
 71336        return type is INamedTypeSymbol namedType &&
 71337            !namedType.IsAbstract &&
 71338            namedType.InstanceConstructors.Any(constructor =>
 101339                constructor.Parameters.Length == 0 &&
 101340                constructor.DeclaredAccessibility == Accessibility.Public);
 1341    }
 1342
 1343    private static bool InheritsFromSystemAttribute(INamedTypeSymbol type)
 1344    {
 381345        var current = type.BaseType;
 401346        while (current is not null)
 1347        {
 381348            if (current.ToDisplayString() == "System.Attribute")
 361349                return true;
 1350
 21351            current = current.BaseType;
 1352        }
 1353
 21354        return false;
 1355    }
 1356}

Methods/Properties

BuildDirectGuardOccurrence(Microsoft.CodeAnalysis.ISymbol,Microsoft.CodeAnalysis.ITypeSymbol,System.String,Microsoft.CodeAnalysis.AttributeData,System.String)
BuildAliasOccurrence(Microsoft.CodeAnalysis.ISymbol,Microsoft.CodeAnalysis.ITypeSymbol,System.String,Microsoft.CodeAnalysis.AttributeData,System.String,Microsoft.CodeAnalysis.ITypeSymbol,System.String,System.Boolean,System.Boolean)
TryGetGuardDefinition(Microsoft.CodeAnalysis.INamedTypeSymbol,Microsoft.CodeAnalysis.ITypeSymbol&,System.String&,System.Boolean&)
AnalyzePositiveGuardOccurrence(Microsoft.CodeAnalysis.Diagnostics.SyntaxNodeAnalysisContext,Microsoft.CodeAnalysis.INamedTypeSymbol,NexusLabs.Needlr.Generators.Models.ConstructorGuardOccurrence,Microsoft.CodeAnalysis.Location)
IsPositiveGuardOccurrenceValidForGeneration(Microsoft.CodeAnalysis.Compilation,Microsoft.CodeAnalysis.INamedTypeSymbol,NexusLabs.Needlr.Generators.Models.ConstructorGuardOccurrence)
TryReportUndefinedEnum(Microsoft.CodeAnalysis.Diagnostics.SyntaxNodeAnalysisContext,Microsoft.CodeAnalysis.Location,Microsoft.CodeAnalysis.TypedConstant)
TryResolveGuardMethod(Microsoft.CodeAnalysis.Compilation,Microsoft.CodeAnalysis.INamedTypeSymbol,Microsoft.CodeAnalysis.ITypeSymbol,System.String,Microsoft.CodeAnalysis.ITypeSymbol,System.String,System.Collections.Immutable.ImmutableArray`1<Microsoft.CodeAnalysis.ITypeSymbol>,Microsoft.CodeAnalysis.IMethodSymbol&,System.String&,NexusLabs.Needlr.Generators.Models.GuardResolutionFailureKind&)
IsBetterGuardMethod(Microsoft.CodeAnalysis.IMethodSymbol,Microsoft.CodeAnalysis.IMethodSymbol,Microsoft.CodeAnalysis.ITypeSymbol,System.Collections.Immutable.ImmutableArray`1<Microsoft.CodeAnalysis.ITypeSymbol>,Microsoft.CodeAnalysis.Compilation)
GetGuardDefinitionTargetInvalidReason(Microsoft.CodeAnalysis.INamedTypeSymbol)
GetAttributeLocation(Microsoft.CodeAnalysis.Diagnostics.SyntaxNodeAnalysisContext,Microsoft.CodeAnalysis.AttributeData)
CreateOccurrence(Microsoft.CodeAnalysis.ISymbol,Microsoft.CodeAnalysis.ITypeSymbol,System.String,Microsoft.CodeAnalysis.AttributeData,NexusLabs.Needlr.Generators.Models.ConstructorGuardOccurrenceKind,System.String,Microsoft.CodeAnalysis.ITypeSymbol,System.String,System.Boolean,System.Boolean)
IsCustomGuardValidForGeneration(Microsoft.CodeAnalysis.Compilation,Microsoft.CodeAnalysis.INamedTypeSymbol,NexusLabs.Needlr.Generators.Models.ConstructorGuardOccurrence,System.Collections.Immutable.ImmutableArray`1<Microsoft.CodeAnalysis.ITypeSymbol>)
IsDefinedEnumValue(Microsoft.CodeAnalysis.TypedConstant)
AnalyzeAliasGuardAtUsage(Microsoft.CodeAnalysis.Diagnostics.SyntaxNodeAnalysisContext,Microsoft.CodeAnalysis.INamedTypeSymbol,NexusLabs.Needlr.Generators.Models.ConstructorGuardOccurrence,Microsoft.CodeAnalysis.Location)
AnalyzeBuiltInGuard(Microsoft.CodeAnalysis.Diagnostics.SyntaxNodeAnalysisContext,NexusLabs.Needlr.Generators.Models.ConstructorGuardOccurrence,Microsoft.CodeAnalysis.Location)
AnalyzeCustomGuard(Microsoft.CodeAnalysis.Diagnostics.SyntaxNodeAnalysisContext,Microsoft.CodeAnalysis.INamedTypeSymbol,NexusLabs.Needlr.Generators.Models.ConstructorGuardOccurrence,Microsoft.CodeAnalysis.Location,Microsoft.CodeAnalysis.ITypeSymbol,System.String,System.Boolean,System.Collections.Immutable.ImmutableArray`1<Microsoft.CodeAnalysis.ITypeSymbol>)
TryGetForwardedArgumentTypes(Microsoft.CodeAnalysis.AttributeData,System.Collections.Immutable.ImmutableArray`1<Microsoft.CodeAnalysis.ITypeSymbol>&,System.String&)
DescribeUnsupportedConstant(Microsoft.CodeAnalysis.TypedConstant)
CanBeRuntimeNull(Microsoft.CodeAnalysis.ITypeSymbol)
IsAssignableTo(Microsoft.CodeAnalysis.ITypeSymbol,Microsoft.CodeAnalysis.ITypeSymbol,Microsoft.CodeAnalysis.Compilation)
TryCheckNonGenericCompatibility(Microsoft.CodeAnalysis.ITypeSymbol,System.String,Microsoft.CodeAnalysis.ITypeSymbol,System.Collections.Generic.List`1<Microsoft.CodeAnalysis.IParameterSymbol>,System.Collections.Immutable.ImmutableArray`1<Microsoft.CodeAnalysis.ITypeSymbol>,Microsoft.CodeAnalysis.Compilation,System.String&,NexusLabs.Needlr.Generators.Models.GuardResolutionFailureKind&)
TryInferGenericParameterCompatibility(Microsoft.CodeAnalysis.IMethodSymbol,Microsoft.CodeAnalysis.ITypeSymbol,Microsoft.CodeAnalysis.ITypeSymbol,System.String,System.Collections.Generic.List`1<Microsoft.CodeAnalysis.IParameterSymbol>,System.Collections.Immutable.ImmutableArray`1<Microsoft.CodeAnalysis.ITypeSymbol>,Microsoft.CodeAnalysis.Compilation,System.String&,NexusLabs.Needlr.Generators.Models.GuardResolutionFailureKind&)
GetMemberTypeIncompatibleReason(System.String)
GetMemberKindPlural(System.String)
ContainsTypeParameter(Microsoft.CodeAnalysis.ITypeSymbol,Microsoft.CodeAnalysis.ITypeParameterSymbol)
IsReachableOnlyThroughForwardedParameters(Microsoft.CodeAnalysis.ITypeParameterSymbol,Microsoft.CodeAnalysis.ITypeSymbol,System.Collections.Generic.List`1<Microsoft.CodeAnalysis.IParameterSymbol>)
FindConstraintViolation(System.Collections.Immutable.ImmutableArray`1<Microsoft.CodeAnalysis.ITypeParameterSymbol>,System.Collections.Generic.Dictionary`2<Microsoft.CodeAnalysis.ITypeSymbol,Microsoft.CodeAnalysis.ITypeSymbol>,Microsoft.CodeAnalysis.Compilation,Microsoft.CodeAnalysis.ITypeParameterSymbol&)
GetTypeAndSupertypes()
TryUnify(Microsoft.CodeAnalysis.ITypeSymbol,Microsoft.CodeAnalysis.ITypeSymbol,System.Collections.Generic.HashSet`1<Microsoft.CodeAnalysis.ITypeSymbol>,System.Collections.Generic.Dictionary`2<Microsoft.CodeAnalysis.ITypeSymbol,Microsoft.CodeAnalysis.ITypeSymbol>,Microsoft.CodeAnalysis.Compilation,System.Boolean)
IsNullableValueType(Microsoft.CodeAnalysis.ITypeSymbol)
SatisfiesConstructorConstraint(Microsoft.CodeAnalysis.ITypeSymbol)
InheritsFromSystemAttribute(Microsoft.CodeAnalysis.INamedTypeSymbol)