|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using SourceGenerators;
namespace Microsoft.Extensions.Configuration.Binder.SourceGeneration
{
public sealed partial class ConfigurationBindingGenerator : IIncrementalGenerator
{
internal sealed partial class Parser(CompilationData compilationData)
{
private readonly KnownTypeSymbols _typeSymbols = compilationData.TypeSymbols!;
private readonly bool _langVersionIsSupported = compilationData.LanguageVersionIsSupported;
private readonly List<TypeParseInfo> _invocationTypeParseInfo = new();
private readonly Queue<TypeParseInfo> _typesToParse = new();
private readonly Dictionary<ITypeSymbol, TypeSpec> _createdTypeSpecs = new(SymbolEqualityComparer.Default);
private readonly InterceptorInfo.Builder _interceptorInfoBuilder = new();
private BindingHelperInfo.Builder? _helperInfoBuilder; // Init'ed with type index when registering interceptors, after creating type specs.
private bool _emitEnumParseMethod;
private bool _emitGenericParseEnum;
public List<DiagnosticInfo>? Diagnostics { get; private set; }
public SourceGenerationSpec? GetSourceGenerationSpec(ImmutableArray<BinderInvocation?> invocations, CancellationToken cancellationToken)
{
if (!_langVersionIsSupported)
{
RecordDiagnostic(DiagnosticDescriptors.LanguageVersionNotSupported, trimmedLocation: Location.None);
return null;
}
if (_typeSymbols is not { IConfiguration: { }, ConfigurationBinder: { } })
{
return null;
}
ParseInvocations(invocations);
CreateTypeSpecs(cancellationToken);
RegisterInterceptors();
CheckIfToEmitParseEnumMethod();
return new SourceGenerationSpec
{
InterceptorInfo = _interceptorInfoBuilder.ToIncrementalValue(),
BindingHelperInfo = _helperInfoBuilder!.ToIncrementalValue(),
ConfigTypes = _createdTypeSpecs.Values.OrderBy(s => s.TypeRef.FullyQualifiedName).ToImmutableEquatableArray(),
EmitEnumParseMethod = _emitEnumParseMethod,
EmitGenericParseEnum = _emitGenericParseEnum,
EmitThrowIfNullMethod = IsThrowIfNullMethodToBeEmitted()
};
}
private bool IsValidRootConfigType([NotNullWhen(true)] ITypeSymbol? type)
{
if (type is null ||
type.SpecialType is SpecialType.System_Object or SpecialType.System_Void ||
!_typeSymbols.Compilation.IsSymbolAccessibleWithin(type, _typeSymbols.Compilation.Assembly) ||
type.TypeKind is TypeKind.TypeParameter or TypeKind.Pointer or TypeKind.Error ||
type.IsRefLikeType ||
ContainsGenericParameters(type))
{
return false;
}
return true;
}
private void ParseInvocations(ImmutableArray<BinderInvocation?> invocations)
{
foreach (BinderInvocation? invocation in invocations)
{
Debug.Assert(invocation is not null);
IMethodSymbol targetMethod = invocation.Operation.TargetMethod;
INamedTypeSymbol? candidateBinderType = targetMethod.ContainingType;
Debug.Assert(targetMethod.IsExtensionMethod);
if (SymbolEqualityComparer.Default.Equals(candidateBinderType, _typeSymbols.ConfigurationBinder))
{
ParseInvocation_ConfigurationBinder(invocation);
}
else if (SymbolEqualityComparer.Default.Equals(candidateBinderType, _typeSymbols.OptionsBuilderConfigurationExtensions))
{
ParseInvocation_OptionsBuilderExt(invocation);
}
else if (SymbolEqualityComparer.Default.Equals(candidateBinderType, _typeSymbols.OptionsConfigurationServiceCollectionExtensions))
{
ParseInvocation_ServiceCollectionExt(invocation);
}
}
}
private void CreateTypeSpecs(CancellationToken cancellationToken)
{
while (_typesToParse.Count > 0)
{
cancellationToken.ThrowIfCancellationRequested();
TypeParseInfo typeParseInfo = _typesToParse.Dequeue();
ITypeSymbol typeSymbol = typeParseInfo.TypeSymbol;
if (!_createdTypeSpecs.ContainsKey(typeSymbol))
{
_createdTypeSpecs.Add(typeSymbol, CreateTypeSpec(typeParseInfo));
}
}
}
private void RegisterInterceptors()
{
TypeIndex typeIndex = new(_createdTypeSpecs.Values);
_helperInfoBuilder = new(typeIndex);
foreach (TypeParseInfo typeParseInfo in _invocationTypeParseInfo)
{
TypeSpec typeSpec = _createdTypeSpecs[typeParseInfo.TypeSymbol];
MethodsToGen overload = typeParseInfo.BindingOverload;
if ((MethodsToGen.ConfigBinder_Any & overload) is not 0)
{
RegisterInterceptor_ConfigurationBinder(typeParseInfo, typeSpec);
}
else if ((MethodsToGen.OptionsBuilderExt_Any & overload) is not 0)
{
RegisterInterceptor_OptionsBuilderExt(typeParseInfo, typeSpec);
}
else
{
Debug.Assert((MethodsToGen.ServiceCollectionExt_Any & overload) is not 0);
RegisterInterceptor_ServiceCollectionExt(typeParseInfo, typeSpec);
}
}
}
private void EnqueueTargetTypeForRootInvocation(ITypeSymbol? typeSymbol, MethodsToGen overload, BinderInvocation invocation)
{
if (!IsValidRootConfigType(typeSymbol))
{
RecordDiagnostic(DiagnosticDescriptors.CouldNotDetermineTypeInfo, invocation.Location);
}
else
{
TypeParseInfo typeParseInfo = TypeParseInfo.Create(typeSymbol, overload, invocation, containingTypeDiagInfo: null);
_typesToParse.Enqueue(typeParseInfo);
_invocationTypeParseInfo.Add(typeParseInfo);
}
}
private TypeRef EnqueueTransitiveType(TypeParseInfo containingTypeParseInfo, ITypeSymbol memberTypeSymbol, DiagnosticDescriptor diagDescriptor, string? memberName = null)
{
TypeParseInfo memberTypeParseInfo = containingTypeParseInfo.ToTransitiveTypeParseInfo(memberTypeSymbol, diagDescriptor, memberName);
if (_createdTypeSpecs.TryGetValue(memberTypeSymbol, out TypeSpec? memberTypeSpec))
{
RecordTypeDiagnosticIfRequired(memberTypeParseInfo, memberTypeSpec);
return memberTypeSpec.TypeRef;
}
_typesToParse.Enqueue(memberTypeParseInfo);
return new TypeRef(memberTypeSymbol);
}
private TypeSpec CreateTypeSpec(TypeParseInfo typeParseInfo)
{
ITypeSymbol type = typeParseInfo.TypeSymbol;
TypeSpec spec;
if (IsNullable(type, out ITypeSymbol? underlyingType))
{
TypeRef underlyingTypeRef = EnqueueTransitiveType(
typeParseInfo,
underlyingType,
DiagnosticDescriptors.NullableUnderlyingTypeNotSupported);
spec = new NullableSpec(type, underlyingTypeRef);
}
else if (IsParsableFromString(type, out StringParsableTypeKind specialTypeKind))
{
ParsableFromStringSpec stringParsableSpec = new(type) { StringParsableTypeKind = specialTypeKind };
spec = stringParsableSpec;
}
else if (type.TypeKind is TypeKind.Array)
{
spec = CreateArraySpec(typeParseInfo);
Debug.Assert(spec is ArraySpec or UnsupportedTypeSpec);
}
else if (IsCollection(type))
{
spec = CreateCollectionSpec(typeParseInfo);
}
else if (SymbolEqualityComparer.Default.Equals(type, _typeSymbols.IConfigurationSection))
{
spec = new ConfigurationSectionSpec(type);
}
else if (type is INamedTypeSymbol)
{
spec = CreateObjectSpec(typeParseInfo);
}
else
{
spec = CreateUnsupportedTypeSpec(typeParseInfo, NotSupportedReason.UnknownType);
}
RecordTypeDiagnosticIfRequired(typeParseInfo, spec);
return spec;
}
private static bool IsNullable(ITypeSymbol type, [NotNullWhen(true)] out ITypeSymbol? underlyingType)
{
if (type is INamedTypeSymbol { IsGenericType: true } genericType &&
genericType.ConstructUnboundGenericType() is INamedTypeSymbol { } unboundGeneric &&
unboundGeneric.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
{
underlyingType = genericType.TypeArguments[0];
return true;
}
underlyingType = null;
return false;
}
private bool IsParsableFromString(ITypeSymbol type, out StringParsableTypeKind typeKind)
{
if (type is IArrayTypeSymbol { ElementType.SpecialType: SpecialType.System_Byte })
{
typeKind = StringParsableTypeKind.ByteArray;
return true;
}
if (type is not INamedTypeSymbol namedType)
{
typeKind = StringParsableTypeKind.None;
return false;
}
if (IsEnum(namedType))
{
typeKind = StringParsableTypeKind.Enum;
return true;
}
SpecialType specialType = namedType.SpecialType;
switch (specialType)
{
case SpecialType.System_String:
case SpecialType.System_Object:
{
typeKind = StringParsableTypeKind.AssignFromSectionValue;
return true;
}
case SpecialType.System_Boolean:
case SpecialType.System_Char:
{
typeKind = StringParsableTypeKind.Parse;
return true;
}
case SpecialType.System_Single:
case SpecialType.System_Double:
case SpecialType.System_Decimal:
{
typeKind = StringParsableTypeKind.Float;
return true;
}
case SpecialType.System_Byte:
case SpecialType.System_Int16:
case SpecialType.System_Int32:
case SpecialType.System_Int64:
case SpecialType.System_SByte:
case SpecialType.System_UInt16:
case SpecialType.System_UInt32:
case SpecialType.System_UInt64:
{
typeKind = StringParsableTypeKind.Integer;
return true;
}
case SpecialType.System_DateTime:
{
typeKind = StringParsableTypeKind.ParseInvariant;
return true;
}
case SpecialType.None:
{
if (SymbolEqualityComparer.Default.Equals(type, _typeSymbols.CultureInfo))
{
typeKind = StringParsableTypeKind.CultureInfo;
}
else if (SymbolEqualityComparer.Default.Equals(type, _typeSymbols.DateTimeOffset) ||
SymbolEqualityComparer.Default.Equals(type, _typeSymbols.DateOnly) ||
SymbolEqualityComparer.Default.Equals(type, _typeSymbols.TimeOnly) ||
SymbolEqualityComparer.Default.Equals(type, _typeSymbols.TimeSpan))
{
typeKind = StringParsableTypeKind.ParseInvariant;
}
else if (SymbolEqualityComparer.Default.Equals(type, _typeSymbols.Int128) ||
SymbolEqualityComparer.Default.Equals(type, _typeSymbols.Half) ||
SymbolEqualityComparer.Default.Equals(type, _typeSymbols.UInt128))
{
typeKind = StringParsableTypeKind.ParseInvariant;
}
else if (SymbolEqualityComparer.Default.Equals(type, _typeSymbols.Uri))
{
typeKind = StringParsableTypeKind.Uri;
}
else if (SymbolEqualityComparer.Default.Equals(type, _typeSymbols.Version) ||
SymbolEqualityComparer.Default.Equals(type, _typeSymbols.Guid))
{
typeKind = StringParsableTypeKind.Parse;
}
else
{
typeKind = StringParsableTypeKind.None;
return false;
}
return true;
}
default:
{
typeKind = StringParsableTypeKind.None;
return false;
}
}
}
private TypeSpec CreateArraySpec(TypeParseInfo typeParseInfo)
{
IArrayTypeSymbol typeSymbol = (IArrayTypeSymbol)typeParseInfo.TypeSymbol;
if (typeSymbol.Rank > 1)
{
return CreateUnsupportedTypeSpec(typeParseInfo, NotSupportedReason.MultiDimArraysNotSupported);
}
if (IsUnsupportedType(typeSymbol.ElementType))
{
return CreateUnsupportedCollectionSpec(typeParseInfo);
}
TypeRef elementTypeRef = EnqueueTransitiveType(
typeParseInfo,
typeSymbol.ElementType,
DiagnosticDescriptors.ElementTypeNotSupported);
return new ArraySpec(typeSymbol)
{
ElementTypeRef = elementTypeRef,
};
}
private TypeSpec CreateCollectionSpec(TypeParseInfo typeParseInfo)
{
INamedTypeSymbol type = (INamedTypeSymbol)typeParseInfo.TypeSymbol;
TypeSpec spec;
if (IsCandidateDictionary(type, out ITypeSymbol? keyType, out ITypeSymbol? elementType))
{
spec = CreateDictionarySpec(typeParseInfo, keyType, elementType);
Debug.Assert(spec is DictionarySpec or UnsupportedTypeSpec);
}
else
{
spec = CreateEnumerableSpec(typeParseInfo);
Debug.Assert(spec is EnumerableSpec or UnsupportedTypeSpec);
}
return spec;
}
private TypeSpec CreateDictionarySpec(TypeParseInfo typeParseInfo, ITypeSymbol keyTypeSymbol, ITypeSymbol elementTypeSymbol)
{
if (IsUnsupportedType(keyTypeSymbol) || IsUnsupportedType(elementTypeSymbol))
{
return CreateUnsupportedCollectionSpec(typeParseInfo);
}
INamedTypeSymbol type = (INamedTypeSymbol)typeParseInfo.TypeSymbol;
CollectionInstantiationStrategy instantiationStrategy;
CollectionInstantiationConcreteType instantiationConcreteType;
CollectionPopulationCastType populationCastType;
if (HasPublicParameterLessCtor(type))
{
instantiationStrategy = CollectionInstantiationStrategy.ParameterlessConstructor;
instantiationConcreteType = CollectionInstantiationConcreteType.Self;
if (HasAddMethod(type, keyTypeSymbol, elementTypeSymbol))
{
populationCastType = CollectionPopulationCastType.NotApplicable;
}
else if (_typeSymbols.GenericIDictionary is not null && GetInterface(type, _typeSymbols.GenericIDictionary_Unbound) is not null)
{
populationCastType = CollectionPopulationCastType.IDictionary;
}
else
{
return CreateUnsupportedCollectionSpec(typeParseInfo);
}
}
else if (_typeSymbols.Dictionary is not null &&
(IsInterfaceMatch(type, _typeSymbols.GenericIDictionary_Unbound) || IsInterfaceMatch(type, _typeSymbols.IDictionary)))
{
instantiationStrategy = CollectionInstantiationStrategy.ParameterlessConstructor;
instantiationConcreteType = CollectionInstantiationConcreteType.Dictionary;
populationCastType = CollectionPopulationCastType.NotApplicable;
}
else if (_typeSymbols.Dictionary is not null && IsInterfaceMatch(type, _typeSymbols.IReadOnlyDictionary_Unbound))
{
instantiationStrategy = CollectionInstantiationStrategy.LinqToDictionary;
instantiationConcreteType = CollectionInstantiationConcreteType.Dictionary;
populationCastType = CollectionPopulationCastType.IDictionary;
}
else
{
return CreateUnsupportedCollectionSpec(typeParseInfo);
}
TypeRef keyTypeRef = EnqueueTransitiveType(typeParseInfo, keyTypeSymbol, DiagnosticDescriptors.DictionaryKeyNotSupported);
TypeRef elementTypeRef = EnqueueTransitiveType(typeParseInfo, elementTypeSymbol, DiagnosticDescriptors.ElementTypeNotSupported);
return new DictionarySpec(type)
{
KeyTypeRef = keyTypeRef,
ElementTypeRef = elementTypeRef,
InstantiationStrategy = instantiationStrategy,
InstantiationConcreteType = instantiationConcreteType,
PopulationCastType = populationCastType,
};
}
private TypeSpec CreateEnumerableSpec(TypeParseInfo typeParseInfo)
{
INamedTypeSymbol type = (INamedTypeSymbol)typeParseInfo.TypeSymbol;
if (!TryGetElementType(type, out ITypeSymbol? elementType))
{
return CreateUnsupportedCollectionSpec(typeParseInfo);
}
if (IsUnsupportedType(elementType))
{
return CreateUnsupportedCollectionSpec(typeParseInfo);
}
CollectionInstantiationStrategy instantiationStrategy;
CollectionInstantiationConcreteType instantiationConcreteType;
CollectionPopulationCastType populationCastType;
if (HasPublicParameterLessCtor(type))
{
instantiationStrategy = CollectionInstantiationStrategy.ParameterlessConstructor;
instantiationConcreteType = CollectionInstantiationConcreteType.Self;
if (HasAddMethod(type, elementType))
{
populationCastType = CollectionPopulationCastType.NotApplicable;
}
else if (_typeSymbols.GenericICollection is not null && GetInterface(type, _typeSymbols.GenericICollection_Unbound) is not null)
{
populationCastType = CollectionPopulationCastType.ICollection;
}
else
{
return CreateUnsupportedCollectionSpec(typeParseInfo);
}
}
else if ((IsInterfaceMatch(type, _typeSymbols.GenericICollection_Unbound) || IsInterfaceMatch(type, _typeSymbols.GenericIList_Unbound)))
{
instantiationStrategy = CollectionInstantiationStrategy.ParameterlessConstructor;
instantiationConcreteType = CollectionInstantiationConcreteType.List;
populationCastType = CollectionPopulationCastType.NotApplicable;
}
else if (IsInterfaceMatch(type, _typeSymbols.GenericIEnumerable_Unbound))
{
instantiationStrategy = CollectionInstantiationStrategy.CopyConstructor;
instantiationConcreteType = CollectionInstantiationConcreteType.List;
populationCastType = CollectionPopulationCastType.ICollection;
}
else if (IsInterfaceMatch(type, _typeSymbols.ISet_Unbound))
{
instantiationStrategy = CollectionInstantiationStrategy.ParameterlessConstructor;
instantiationConcreteType = CollectionInstantiationConcreteType.HashSet;
populationCastType = CollectionPopulationCastType.NotApplicable;
}
else if (IsInterfaceMatch(type, _typeSymbols.IReadOnlySet_Unbound))
{
instantiationStrategy = CollectionInstantiationStrategy.CopyConstructor;
instantiationConcreteType = CollectionInstantiationConcreteType.HashSet;
populationCastType = CollectionPopulationCastType.ISet;
}
else if (IsInterfaceMatch(type, _typeSymbols.IReadOnlyList_Unbound) || IsInterfaceMatch(type, _typeSymbols.IReadOnlyCollection_Unbound))
{
instantiationStrategy = CollectionInstantiationStrategy.CopyConstructor;
instantiationConcreteType = CollectionInstantiationConcreteType.List;
populationCastType = CollectionPopulationCastType.ICollection;
}
else
{
return CreateUnsupportedCollectionSpec(typeParseInfo);
}
TypeRef elementTypeRef = EnqueueTransitiveType(typeParseInfo, elementType, DiagnosticDescriptors.ElementTypeNotSupported);
return new EnumerableSpec(type)
{
ElementTypeRef = elementTypeRef,
InstantiationStrategy = instantiationStrategy,
InstantiationConcreteType = instantiationConcreteType,
PopulationCastType = populationCastType,
};
}
private bool IsAssignableTo(ITypeSymbol source, ITypeSymbol dest)
{
Conversion conversion = _typeSymbols.Compilation.ClassifyConversion(source, dest);
return conversion.IsReference && conversion.IsImplicit;
}
private bool IsUnsupportedType(ITypeSymbol type, int recursionDepth = 0)
{
recursionDepth++;
if (recursionDepth > 10)
{
// Prevent stack overflow on recursive types, such as:
// public sealed class TreeElement : Dictionary<string, TreeElement>
return false;
}
if (type.OriginalDefinition.SpecialType == SpecialType.System_Nullable_T)
{
type = ((INamedTypeSymbol)type).TypeArguments[0]; // extract the T from a Nullable<T>
}
if (SymbolEqualityComparer.Default.Equals(_typeSymbols.IntPtr, type) ||
SymbolEqualityComparer.Default.Equals(_typeSymbols.UIntPtr, type) ||
SymbolEqualityComparer.Default.Equals(_typeSymbols.SerializationInfo, type) ||
SymbolEqualityComparer.Default.Equals(_typeSymbols.ParameterInfo, type) ||
IsAssignableTo(type, _typeSymbols.MemberInfo) ||
IsAssignableTo(type, _typeSymbols.Delegate))
{
return true;
}
if (type is IArrayTypeSymbol arrayTypeSymbol)
{
return arrayTypeSymbol.Rank > 1 || IsUnsupportedType(arrayTypeSymbol.ElementType, recursionDepth);
}
if (IsCollection(type))
{
INamedTypeSymbol collectionType = (INamedTypeSymbol)type;
if (IsCandidateDictionary(collectionType, out ITypeSymbol? keyType, out ITypeSymbol? elementType))
{
return IsUnsupportedType(keyType, recursionDepth) || IsUnsupportedType(elementType, recursionDepth);
}
else if (TryGetElementType(collectionType, out elementType))
{
return IsUnsupportedType(elementType, recursionDepth);
}
}
return false;
}
private bool ConstructorParametersContainUnsupportedType(IMethodSymbol ctor)
{
foreach (IParameterSymbol parameter in ctor.Parameters)
{
if (IsUnsupportedType(parameter.Type))
{
return true;
}
}
return false;
}
private ObjectSpec CreateObjectSpec(TypeParseInfo typeParseInfo)
{
INamedTypeSymbol typeSymbol = (INamedTypeSymbol)typeParseInfo.TypeSymbol;
ObjectInstantiationStrategy initializationStrategy = ObjectInstantiationStrategy.None;
DiagnosticDescriptor? initDiagDescriptor = null;
string? initExceptionMessage = null;
IMethodSymbol? ctor = null;
if (!(typeSymbol.IsAbstract || typeSymbol.TypeKind is TypeKind.Interface))
{
IMethodSymbol? parameterlessCtor = null;
IMethodSymbol? parameterizedCtor = null;
bool hasMultipleParameterizedCtors = false;
foreach (IMethodSymbol candidate in typeSymbol.InstanceConstructors)
{
if (candidate.DeclaredAccessibility is not Accessibility.Public)
{
continue;
}
if (candidate.Parameters.Length is 0)
{
parameterlessCtor = candidate;
}
else if (!ConstructorParametersContainUnsupportedType(candidate))
{
if (parameterizedCtor is not null)
{
hasMultipleParameterizedCtors = true;
}
else
{
parameterizedCtor = candidate;
}
}
}
bool hasPublicParameterlessCtor = typeSymbol.IsValueType || parameterlessCtor is not null;
if (!hasPublicParameterlessCtor && hasMultipleParameterizedCtors)
{
initDiagDescriptor = DiagnosticDescriptors.MultipleParameterizedConstructors;
initExceptionMessage = string.Format(Emitter.ExceptionMessages.MultipleParameterizedConstructors, typeSymbol.GetFullName());
}
ctor = typeSymbol.IsValueType
// Roslyn ctor fetching APIs include parameterless ctors for structs, unlike System.Reflection.
? parameterizedCtor ?? parameterlessCtor
: parameterlessCtor ?? parameterizedCtor;
}
if (ctor is null)
{
initDiagDescriptor = DiagnosticDescriptors.MissingPublicInstanceConstructor;
initExceptionMessage = string.Format(Emitter.ExceptionMessages.MissingPublicInstanceConstructor, typeSymbol.GetFullName());
}
else
{
initializationStrategy = ctor.Parameters.Length is 0 ? ObjectInstantiationStrategy.ParameterlessConstructor : ObjectInstantiationStrategy.ParameterizedConstructor;
}
if (initDiagDescriptor is not null)
{
Debug.Assert(initExceptionMessage is not null);
RecordTypeDiagnostic(typeParseInfo, initDiagDescriptor);
}
Dictionary<string, PropertySpec>? properties = null;
INamedTypeSymbol? current = typeSymbol;
while (current is not null)
{
ImmutableArray<ISymbol> members = current.GetMembers();
foreach (ISymbol member in members)
{
if (member is IPropertySymbol { IsIndexer: false, IsImplicitlyDeclared: false } property && !IsUnsupportedType(property.Type))
{
string propertyName = property.Name;
TypeRef propertyTypeRef = EnqueueTransitiveType(typeParseInfo, property.Type, DiagnosticDescriptors.PropertyNotSupported, propertyName);
AttributeData? attributeData = property.GetAttributes().FirstOrDefault(a => SymbolEqualityComparer.Default.Equals(a.AttributeClass, _typeSymbols.ConfigurationKeyNameAttribute));
string configKeyName = attributeData?.ConstructorArguments.FirstOrDefault().Value as string ?? propertyName;
PropertySpec spec = new(property, propertyTypeRef)
{
ConfigurationKeyName = configKeyName
};
(properties ??= new(StringComparer.OrdinalIgnoreCase))[propertyName] = spec;
}
}
current = current.BaseType;
}
List<ParameterSpec>? ctorParams = null;
if (initializationStrategy is ObjectInstantiationStrategy.ParameterizedConstructor)
{
Debug.Assert(ctor is not null);
List<string>? missingParameters = null;
List<string>? invalidParameters = null;
foreach (IParameterSymbol parameter in ctor.Parameters)
{
string parameterName = parameter.Name;
if (properties?.TryGetValue(parameterName, out PropertySpec? propertySpec) is not true)
{
(missingParameters ??= new()).Add(parameterName);
}
else if (parameter.RefKind is not RefKind.None)
{
(invalidParameters ??= new()).Add(parameterName);
}
else
{
ParameterSpec paramSpec = new ParameterSpec(parameter, propertySpec.TypeRef)
{
ConfigurationKeyName = propertySpec.ConfigurationKeyName,
};
propertySpec.MatchingCtorParam = paramSpec;
(ctorParams ??= new()).Add(paramSpec);
}
}
if (invalidParameters?.Count > 0)
{
initExceptionMessage = string.Format(Emitter.ExceptionMessages.CannotBindToConstructorParameter, typeSymbol.GetFullName(), FormatParams(invalidParameters));
}
else if (missingParameters?.Count > 0)
{
if (typeSymbol.IsValueType)
{
initializationStrategy = ObjectInstantiationStrategy.ParameterlessConstructor;
}
else
{
initExceptionMessage = string.Format(Emitter.ExceptionMessages.ConstructorParametersDoNotMatchProperties, typeSymbol.GetFullName(), FormatParams(missingParameters));
}
}
static string FormatParams(List<string> names) => string.Join(",", names);
}
return new ObjectSpec(
typeSymbol,
initializationStrategy,
properties: properties?.Values.ToImmutableEquatableArray(),
constructorParameters: ctorParams?.ToImmutableEquatableArray(),
initExceptionMessage);
}
private static UnsupportedTypeSpec CreateUnsupportedCollectionSpec(TypeParseInfo typeParseInfo)
=> CreateUnsupportedTypeSpec(typeParseInfo, NotSupportedReason.CollectionNotSupported);
private static UnsupportedTypeSpec CreateUnsupportedTypeSpec(TypeParseInfo typeParseInfo, NotSupportedReason reason) =>
new(typeParseInfo.TypeSymbol) { NotSupportedReason = reason };
private bool TryGetElementType(INamedTypeSymbol type, [NotNullWhen(true)] out ITypeSymbol? elementType)
{
INamedTypeSymbol? candidate = GetInterface(type, _typeSymbols.GenericIEnumerable_Unbound);
if (candidate is not null)
{
elementType = candidate.TypeArguments[0];
return true;
}
elementType = null;
return false;
}
private bool IsCandidateDictionary(INamedTypeSymbol type, [NotNullWhen(true)] out ITypeSymbol? keyType, [NotNullWhen(true)] out ITypeSymbol? elementType)
{
INamedTypeSymbol? candidate = GetInterface(type, _typeSymbols.GenericIDictionary_Unbound) ?? GetInterface(type, _typeSymbols.IReadOnlyDictionary_Unbound);
if (candidate is not null)
{
keyType = candidate.TypeArguments[0];
elementType = candidate.TypeArguments[1];
return true;
}
if (IsInterfaceMatch(type, _typeSymbols.IDictionary))
{
keyType = _typeSymbols.String;
elementType = _typeSymbols.String;
return true;
}
keyType = null;
elementType = null;
return false;
}
private bool IsCollection(ITypeSymbol type) =>
type is INamedTypeSymbol namedType && GetInterface(namedType, _typeSymbols.IEnumerable) is not null;
private static INamedTypeSymbol? GetInterface(INamedTypeSymbol type, INamedTypeSymbol? @interface)
{
if (@interface is null)
{
return null;
}
if (IsInterfaceMatch(type, @interface))
{
return type;
}
if (@interface.IsGenericType)
{
return type.AllInterfaces.FirstOrDefault(candidate =>
candidate.IsGenericType &&
candidate.ConstructUnboundGenericType() is INamedTypeSymbol unbound
&& SymbolEqualityComparer.Default.Equals(unbound, @interface));
}
return type.AllInterfaces.FirstOrDefault(candidate => SymbolEqualityComparer.Default.Equals(candidate, @interface));
}
private static bool IsInterfaceMatch(INamedTypeSymbol type, INamedTypeSymbol? @interface)
{
if (@interface is null)
{
return false;
}
if (type.IsGenericType)
{
INamedTypeSymbol unbound = type.ConstructUnboundGenericType();
return SymbolEqualityComparer.Default.Equals(unbound, @interface);
}
return SymbolEqualityComparer.Default.Equals(type, @interface);
}
private static bool ContainsGenericParameters(ITypeSymbol type)
{
if (type is not INamedTypeSymbol { IsGenericType: true } genericType)
{
return false;
}
foreach (ITypeSymbol typeArg in genericType.TypeArguments)
{
if (typeArg.TypeKind is TypeKind.TypeParameter or TypeKind.Error ||
ContainsGenericParameters(typeArg))
{
return true;
}
}
return false;
}
private static bool HasPublicParameterLessCtor(INamedTypeSymbol type) =>
type.InstanceConstructors.SingleOrDefault(ctor => ctor.DeclaredAccessibility is Accessibility.Public && ctor.Parameters.Length is 0) is not null;
private static bool HasAddMethod(INamedTypeSymbol type, ITypeSymbol element)
{
INamedTypeSymbol? current = type;
while (current is not null)
{
if (current.GetMembers("Add").Any(member =>
member is IMethodSymbol { Parameters.Length: 1 } method &&
SymbolEqualityComparer.Default.Equals(element, method.Parameters[0].Type)))
{
return true;
}
current = current.BaseType;
}
return false;
}
private static bool HasAddMethod(INamedTypeSymbol type, ITypeSymbol key, ITypeSymbol element)
{
INamedTypeSymbol? current = type;
while (current is not null)
{
if (current.GetMembers("Add").Any(member =>
member is IMethodSymbol { Parameters.Length: 2 } method &&
SymbolEqualityComparer.Default.Equals(key, method.Parameters[0].Type) &&
SymbolEqualityComparer.Default.Equals(element, method.Parameters[1].Type)))
{
return true;
}
current = current.BaseType;
}
return false;
}
private static bool IsEnum(ITypeSymbol type) => type is INamedTypeSymbol { EnumUnderlyingType: INamedTypeSymbol { } };
private void RecordTypeDiagnosticIfRequired(TypeParseInfo typeParseInfo, TypeSpec typeSpec)
{
ContainingTypeDiagnosticInfo? containingTypeDiagInfo = typeParseInfo.ContainingTypeDiagnosticInfo;
if (typeSpec is UnsupportedTypeSpec unsupportedTypeSpec)
{
DiagnosticDescriptor descriptor = DiagnosticDescriptors.GetNotSupportedDescriptor(unsupportedTypeSpec.NotSupportedReason);
RecordTypeDiagnostic(typeParseInfo, descriptor);
}
else if (containingTypeDiagInfo?.Descriptor == DiagnosticDescriptors.DictionaryKeyNotSupported &&
typeSpec is not ParsableFromStringSpec)
{
ReportContainingTypeDiagnosticIfRequired(typeParseInfo);
}
}
private void RecordTypeDiagnostic(TypeParseInfo typeParseInfo, DiagnosticDescriptor descriptor)
{
RecordDiagnostic(descriptor, typeParseInfo.BinderInvocation?.Location, [typeParseInfo.FullName]);
ReportContainingTypeDiagnosticIfRequired(typeParseInfo);
}
private void ReportContainingTypeDiagnosticIfRequired(TypeParseInfo typeParseInfo)
{
ContainingTypeDiagnosticInfo? containingTypeDiagInfo = typeParseInfo.ContainingTypeDiagnosticInfo;
while (containingTypeDiagInfo is not null)
{
string containingTypeName = containingTypeDiagInfo.FullName;
object[] messageArgs = containingTypeDiagInfo.MemberName is string memberName
? new[] { memberName, containingTypeName }
: new[] { containingTypeName };
RecordDiagnostic(containingTypeDiagInfo.Descriptor, typeParseInfo.BinderInvocation?.Location, messageArgs);
containingTypeDiagInfo = containingTypeDiagInfo.ContainingTypeInfo;
}
}
private void RecordDiagnostic(DiagnosticDescriptor descriptor, Location trimmedLocation, params object?[]? messageArgs)
{
Diagnostics ??= new List<DiagnosticInfo>();
Diagnostics.Add(DiagnosticInfo.Create(descriptor, trimmedLocation, messageArgs));
}
private void CheckIfToEmitParseEnumMethod()
{
foreach (var typeSymbol in _createdTypeSpecs.Keys)
{
if (IsEnum(typeSymbol))
{
_emitEnumParseMethod = true;
_emitGenericParseEnum = _typeSymbols.Enum.GetMembers("Parse").Any(m => m is IMethodSymbol methodSymbol && methodSymbol.IsGenericMethod);
return;
}
}
}
private bool IsThrowIfNullMethodToBeEmitted()
{
if (_typeSymbols.ArgumentNullException is not null)
{
var throwIfNullMethods = _typeSymbols.ArgumentNullException.GetMembers("ThrowIfNull");
foreach (var throwIfNullMethod in throwIfNullMethods)
{
if (throwIfNullMethod is IMethodSymbol throwIfNullMethodSymbol && throwIfNullMethodSymbol.IsStatic && throwIfNullMethodSymbol.Parameters.Length == 2)
{
var parameters = throwIfNullMethodSymbol.Parameters;
var firstParam = parameters[0];
var secondParam = parameters[1];
if (firstParam.Name == "argument" && firstParam.Type.SpecialType == SpecialType.System_Object
&& secondParam.Name == "paramName" && secondParam.Type.Equals(_typeSymbols.String, SymbolEqualityComparer.Default))
{
return true;
}
}
}
}
return false;
}
}
}
}
|