Skip to content

Commit

Permalink
Fix internal member types for source generation
Browse files Browse the repository at this point in the history
  • Loading branch information
afxres committed Mar 11, 2024
1 parent c8f2d17 commit ddab284
Show file tree
Hide file tree
Showing 9 changed files with 85 additions and 98 deletions.
49 changes: 44 additions & 5 deletions code/Binary.SourceGeneration.Tests/DirectTests/SymbolsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ class Alpha
yield return new object[] { a, "Alpha", "RequiredProperty", true };
}

[Theory(DisplayName = "Is Field Or Property Required")]
[Theory(DisplayName = "Is Required Field Or Property")]
[MemberData(nameof(IsRequiredData))]
public void IsRequiredTest(string source, string typeName, string memberName, bool required)
{
Expand All @@ -526,10 +526,49 @@ public void IsRequiredTest(string source, string typeName, string memberName, bo
.Where(x => ((x as IFieldSymbol)?.Name ?? (x as IPropertySymbol)?.Name) == memberName)
.FirstOrDefault();
Assert.NotNull(member);
var a = Symbols.IsRequired(member);
var b = Symbols.IsRequired(symbol);
Assert.Equal(required, a);
Assert.False(b);
var actual = Symbols.IsRequiredFieldOrProperty(member);
Assert.Equal(required, actual);
}

public static IEnumerable<object[]> IsReadOnlyData()
{
var a =
"""
class Alpha
{
public int Field;
public readonly int ReadOnlyField;
public string? Property { get; set; }
public string? ReadOnlyProperty { get; }
}
""";
yield return new object[] { a, "Alpha", "Field", false };
yield return new object[] { a, "Alpha", "ReadOnlyField", true };
yield return new object[] { a, "Alpha", "Property", false };
yield return new object[] { a, "Alpha", "ReadOnlyProperty", true };
}

[Theory(DisplayName = "Is ReadOnly Field Or Property")]
[MemberData(nameof(IsReadOnlyData))]
public void IsReadOnlyTest(string source, string typeName, string memberName, bool @readonly)
{
var compilation = CompilationModule.CreateCompilation(source);
var tree = compilation.SyntaxTrees.First();
var model = compilation.GetSemanticModel(tree);
var nodes = tree.GetRoot().DescendantNodes();
var declaration = nodes.OfType<TypeDeclarationSyntax>().Single();
var symbol = model.GetDeclaredSymbol(declaration);
Assert.NotNull(symbol);
Assert.Equal(typeName, symbol.Name);
var member = symbol.GetMembers()
.Where(x => ((x as IFieldSymbol)?.Name ?? (x as IPropertySymbol)?.Name) == memberName)
.FirstOrDefault();
Assert.NotNull(member);
var actual = Symbols.IsReadOnlyFieldOrProperty(member);
Assert.Equal(@readonly, actual);
}

public static IEnumerable<object[]> FilterFieldsAndPropertiesData()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,9 @@

public sealed partial class NamedObjectConverterContext
{
private static SymbolNamedMemberInfo GetNamedMember(ISymbol member, string literal, bool isTypeRequired)
private static string? GetCustomNamedKey(SourceGeneratorContext context, ISymbol member)
{
if (member is IFieldSymbol field)
return new SymbolNamedMemberInfo(field, literal, isTypeRequired && (field.IsRequired is false));
else
return new SymbolNamedMemberInfo((IPropertySymbol)member, literal, isTypeRequired && (((IPropertySymbol)member).IsRequired is false));
}

private static void GetCustomNamedMember(SourceGeneratorContext context, ISymbol member, bool isTypeRequired, SortedDictionary<string, SymbolNamedMemberInfo> dictionary)
{
var attribute = context.GetAttribute(member, Constants.NamedKeyAttributeTypeName);
if (attribute is null)
return;
var key = (string)attribute.ConstructorArguments.Single().Value!;
var info = GetNamedMember(member, Symbols.ToLiteral(key), isTypeRequired);
dictionary.Add(key, info);
}

private static void GetSimpleNamedMember(ISymbol member, bool isTypeRequired, SortedDictionary<string, SymbolNamedMemberInfo> dictionary)
{
var key = member.Name;
var info = GetNamedMember(member, Symbols.ToLiteral(key), isTypeRequired);
dictionary.Add(key, info);
return context.GetAttribute(member, Constants.NamedKeyAttributeTypeName)?.ConstructorArguments.Single().Value as string;
}

public static SourceResult Invoke(SourceGeneratorContext context, SourceGeneratorTracker tracker, ITypeSymbol symbol)
Expand All @@ -43,10 +23,15 @@ public static SourceResult Invoke(SourceGeneratorContext context, SourceGenerato
var cancellation = context.CancellationToken;
foreach (var member in typeInfo.FilteredFieldsAndProperties)
{
if (attribute is null)
GetSimpleNamedMember(member, required, dictionary);
else
GetCustomNamedMember(context, member, required, dictionary);
var key = attribute is null
? member.Name
: GetCustomNamedKey(context, member);
if (key is null)
continue;
var literal = Symbols.ToLiteral(key);
var optional = required && (Symbols.IsRequiredFieldOrProperty(member) is false);
var memberInfo = new SymbolNamedMemberInfo(member, literal, optional);
dictionary.Add(key, memberInfo);
cancellation.ThrowIfCancellationRequested();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,9 @@ public sealed partial class TupleObjectConverterContext
{
private static readonly ImmutableArray<string> SystemTupleMemberNames = ImmutableArray.Create(["Item1", "Item2", "Item3", "Item4", "Item5", "Item6", "Item7", "Rest"]);

private static SymbolTupleMemberInfo GetTupleMember(ISymbol member)
private static int GetCustomTupleKey(SourceGeneratorContext context, ISymbol member)
{
if (member is IFieldSymbol field)
return new SymbolTupleMemberInfo(field);
else
return new SymbolTupleMemberInfo((IPropertySymbol)member);
}

private static void GetCustomTupleMember(SourceGeneratorContext context, ISymbol member, SortedDictionary<int, SymbolTupleMemberInfo> dictionary)
{
var attribute = context.GetAttribute(member, Constants.TupleKeyAttributeTypeName);
if (attribute is null)
return;
var key = (int)attribute.ConstructorArguments.Single().Value!;
var info = GetTupleMember(member);
dictionary.Add(key, info);
}

private static void GetSystemTupleMember(ISymbol member, SortedDictionary<int, SymbolTupleMemberInfo> dictionary)
{
var key = SystemTupleMemberNames.IndexOf(member.Name);
if (key is -1)
return;
var info = GetTupleMember(member);
dictionary.Add(key, info);
return context.GetAttribute(member, Constants.TupleKeyAttributeTypeName)?.ConstructorArguments.Single().Value as int? ?? -1;
}

private static ImmutableHashSet<INamedTypeSymbol> CreateResource(Compilation compilation)
Expand Down Expand Up @@ -67,10 +45,13 @@ private static bool IsSystemTuple(SourceGeneratorContext context, ITypeSymbol ty
var cancellation = context.CancellationToken;
foreach (var member in typeInfo.FilteredFieldsAndProperties)
{
if (system)
GetSystemTupleMember(member, dictionary);
else
GetCustomTupleMember(context, member, dictionary);
var key = system
? SystemTupleMemberNames.IndexOf(member.Name)
: GetCustomTupleKey(context, member);
if (key is -1)
continue;
var memberInfo = new SymbolTupleMemberInfo(member);
dictionary.Add(key, memberInfo);
cancellation.ThrowIfCancellationRequested();
}
var members = dictionary.Values.ToImmutableArray();
Expand Down
12 changes: 4 additions & 8 deletions code/Binary.SourceGeneration/SymbolMemberInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,13 @@

using Microsoft.CodeAnalysis;

public class SymbolMemberInfo(ISymbol symbol, ITypeSymbol typeSymbol, bool @readonly)
public class SymbolMemberInfo(ISymbol symbol)
{
public ISymbol Symbol { get; } = symbol;

public ITypeSymbol Type { get; } = typeSymbol;
public ITypeSymbol Type { get; } = symbol is IFieldSymbol field ? field.Type : ((IPropertySymbol)symbol).Type;

public string NameInSourceCode { get; } = Symbols.GetNameInSourceCode(symbol.Name);

public bool IsReadOnly { get; } = @readonly;
public bool IsReadOnly { get; } = Symbols.IsReadOnlyFieldOrProperty(symbol);

public SymbolMemberInfo(IFieldSymbol field) : this(field, field.Type, field.IsReadOnly) { }

public SymbolMemberInfo(IPropertySymbol property) : this(property, property.Type, Symbols.HasPublicSetter(property) is false) { }
public string NameInSourceCode { get; } = Symbols.GetNameInSourceCode(symbol.Name);
}
18 changes: 3 additions & 15 deletions code/Binary.SourceGeneration/SymbolNamedMemberInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,9 @@

using Microsoft.CodeAnalysis;

public class SymbolNamedMemberInfo : SymbolMemberInfo
public class SymbolNamedMemberInfo(ISymbol symbol, string namedKeyLiteral, bool optional) : SymbolMemberInfo(symbol)
{
public bool IsOptional { get; }
public bool IsOptional { get; } = optional;

public string NamedKeyLiteral { get; }

public SymbolNamedMemberInfo(IFieldSymbol field, string namedKeyLiteral, bool optional) : base(field)
{
IsOptional = optional;
NamedKeyLiteral = namedKeyLiteral;
}

public SymbolNamedMemberInfo(IPropertySymbol property, string namedKeyLiteral, bool optional) : base(property)
{
IsOptional = optional;
NamedKeyLiteral = namedKeyLiteral;
}
public string NamedKeyLiteral { get; } = namedKeyLiteral;
}
7 changes: 1 addition & 6 deletions code/Binary.SourceGeneration/SymbolTupleMemberInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,4 @@

using Microsoft.CodeAnalysis;

public class SymbolTupleMemberInfo : SymbolMemberInfo
{
public SymbolTupleMemberInfo(IFieldSymbol field) : base(field) { }

public SymbolTupleMemberInfo(IPropertySymbol property) : base(property) { }
}
public class SymbolTupleMemberInfo(ISymbol symbol) : SymbolMemberInfo(symbol) { }
3 changes: 1 addition & 2 deletions code/Binary.SourceGeneration/SymbolTypeInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,11 @@ public class SymbolTypeInfo(ITypeSymbol symbol, ImmutableSortedSet<string> confl

public static SymbolTypeInfo Create(Compilation compilation, ITypeSymbol symbol, CancellationToken cancellation)
{

var originalMembers = Symbols.GetAllFieldsAndProperties(compilation, symbol, out var conflict, cancellation);
if (conflict.Count is not 0)
return new SymbolTypeInfo(symbol, conflict, ImmutableArray.Create<ISymbol>(), ImmutableArray.Create<ISymbol>(), ImmutableHashSet.Create<ISymbol>(SymbolEqualityComparer.Default));
var filteredMembers = Symbols.FilterFieldsAndProperties(originalMembers, cancellation);
var requiredMembers = originalMembers.Where(Symbols.IsRequired).ToImmutableHashSet(SymbolEqualityComparer.Default);
var requiredMembers = originalMembers.Where(Symbols.IsRequiredFieldOrProperty).ToImmutableHashSet(SymbolEqualityComparer.Default);
return new SymbolTypeInfo(symbol, ImmutableSortedSet.Create<string>(), originalMembers, filteredMembers, requiredMembers);
}
}
2 changes: 1 addition & 1 deletion code/Binary.SourceGeneration/Symbols.Validation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ private static void ValidateMember(SourceGeneratorContext context, string contai

var memberName = member.Name;
var hasKey = namedKeyAttribute is not null || tupleKeyAttribute is not null;
var requiredMemberWithoutKey = hasKey is false && requiredMembers.Count is not 0 && IsRequired(member);
var requiredMemberWithoutKey = hasKey is false && requiredMembers.Count is not 0 && IsRequiredFieldOrProperty(member);
if (requiredMemberWithoutKey && typeAttribute is NamedObjectAttribute)
diagnostics.Add(Constants.RequireNamedKeyAttributeForRequiredMember.With(member, [memberName, containingTypeName]));
if (requiredMemberWithoutKey && typeAttribute is TupleObjectAttribute)
Expand Down
18 changes: 11 additions & 7 deletions code/Binary.SourceGeneration/Symbols.cs
Original file line number Diff line number Diff line change
Expand Up @@ -142,14 +142,18 @@ public static bool IsKeyword(string name)
return SyntaxFacts.GetKeywordKind(name) != SyntaxKind.None || SyntaxFacts.GetContextualKeywordKind(name) != SyntaxKind.None;
}

public static bool IsRequired(ISymbol symbol)
public static bool IsRequiredFieldOrProperty(ISymbol symbol)
{
return symbol switch
{
IFieldSymbol field => field.IsRequired,
IPropertySymbol property => property.IsRequired,
_ => false,
};
return symbol is IFieldSymbol field
? field.IsRequired
: ((IPropertySymbol)symbol).IsRequired;
}

public static bool IsReadOnlyFieldOrProperty(ISymbol symbol)
{
return symbol is IFieldSymbol field
? field.IsReadOnly
: HasPublicSetter((IPropertySymbol)symbol) is false;
}

public static bool IsReturnsByRefOrReturnsByRefReadonly(IPropertySymbol property)
Expand Down

0 comments on commit ddab284

Please sign in to comment.