Skip to content

Commit 54cda71

Browse files
Unify visible member resolution algorithm and disallow conflicting member names. (#301)
1 parent dff55fe commit 54cda71

29 files changed

Lines changed: 698 additions & 272 deletions

src/PolyType.Roslyn/Helpers/RoslynHelpers.cs

Lines changed: 39 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -367,61 +367,65 @@ public static ICollection<ITypeSymbol> GetSortedTypeHierarchy(this ITypeSymbol t
367367
}
368368
}
369369

370-
public static IEnumerable<ISymbol> GetAllMembers(this ITypeSymbol type)
371-
=> type.GetSortedTypeHierarchy().SelectMany(t => t.GetMembers());
372-
373-
public static IEnumerable<(IMethodSymbol Symbol, bool IsAmbiguous)> GetAllMethods(this ITypeSymbol type)
370+
/// <summary>
371+
/// Resolves all members that are visible on the specified type,
372+
/// accounting for overrides and shadowing in the type hierarchy.
373+
/// </summary>
374+
public static IEnumerable<(ISymbol Symbol, bool IsAmbiguous)> ResolveVisibleMembers(this ITypeSymbol type)
374375
{
375-
List<(IMethodSymbol Symbol, bool IsAmbiguous)> methods = [];
376-
Dictionary<string, int> foundSignatures = new();
376+
Dictionary<string, List<int>> membersInScope = new(StringComparer.Ordinal);
377+
List<(ISymbol Symbol, bool IsAmbiguous)> results = [];
377378

379+
// Walk the type hierarchy, from most derived to least derived.
378380
foreach (ITypeSymbol current in type.GetSortedTypeHierarchy())
379381
{
380-
foreach (IMethodSymbol method in current.GetMembers().OfType<IMethodSymbol>())
382+
foreach (ISymbol member in current.GetMembers())
381383
{
382-
// Create a signature key that includes name, parameters, and return type
383-
string signature = GetMethodSignature(method);
384-
if (foundSignatures.TryGetValue(signature, out int index))
384+
// To account for overloads, method identifiers include the method name and parameter types but not the return type.
385+
string identifier = member is IMethodSymbol method
386+
? $"{method.Name}({string.Join(", ", method.Parameters.Select(p => p.Type.GetFullyQualifiedName()))})"
387+
: member.Name;
388+
389+
bool isAmbiguous;
390+
if (membersInScope.TryGetValue(identifier, out List<int> conflictingMemberIndices))
385391
{
386-
IMethodSymbol conflictingMethod = methods[index].Symbol;
387-
if (!current.IsAssignableFrom(conflictingMethod.ContainingType))
392+
if (conflictingMemberIndices.Exists(i => current.IsAssignableFrom(results[i].Symbol.ContainingType)))
388393
{
389-
// We have a method with the same signature in an unrelated type,
390-
// due to diamond ambiguity in an interface hierarchy.
394+
continue; // Member is overridden or shadowed by a more derived type in the hierarchy.
395+
}
391396

392-
Debug.Assert(type.TypeKind is TypeKind.Interface);
393-
methods[index] = (conflictingMethod, IsAmbiguous: true);
394-
methods.Add((method, IsAmbiguous: true));
397+
if (conflictingMemberIndices is [int singleIndex])
398+
{
399+
// We found our first ambiguity, update the previous entry accordingly.
400+
results[singleIndex] = (results[singleIndex].Symbol, IsAmbiguous: true);
395401
}
396402

397-
continue; // This method is shadowed by a derived type, skip it.
403+
// Member forms diamond ambiguity, include in case there is disambiguation on attribute configs.
404+
Debug.Assert(type.TypeKind is TypeKind.Interface);
405+
isAmbiguous = true;
406+
}
407+
else
408+
{
409+
membersInScope[identifier] = conflictingMemberIndices = [];
410+
isAmbiguous = false;
398411
}
399412

400-
foundSignatures.Add(signature, methods.Count);
401-
methods.Add((method, IsAmbiguous: false));
413+
conflictingMemberIndices.Add(results.Count);
414+
results.Add((member, isAmbiguous));
402415
}
403416
}
404417

405-
return methods;
406-
407-
static string GetMethodSignature(IMethodSymbol method)
408-
{
409-
string parameters = string.Join(",", method.Parameters.Select(p => p.Type.ToDisplayString()));
410-
return $"{method.ReturnType.ToDisplayString()} {method.Name}({parameters})";
411-
}
418+
return results;
412419
}
413420

414-
public static IEnumerable<IEventSymbol> GetAllEvents(this ITypeSymbol type)
421+
public static IEnumerable<(TMember Symbol, bool IsAmbiguous)> ResolveVisibleMembers<TMember>(this ITypeSymbol type)
422+
where TMember : ISymbol
415423
{
416-
HashSet<string> foundNames = [];
417-
foreach (ITypeSymbol current in type.GetSortedTypeHierarchy())
424+
foreach ((ISymbol symbol, bool isAmbiguous) in type.ResolveVisibleMembers())
418425
{
419-
foreach (IEventSymbol eventSymbol in current.GetMembers().OfType<IEventSymbol>())
426+
if (symbol is TMember member)
420427
{
421-
if (foundNames.Add(eventSymbol.Name))
422-
{
423-
yield return eventSymbol;
424-
}
428+
yield return (member, isAmbiguous);
425429
}
426430
}
427431
}

src/PolyType.Roslyn/Model/EventDataModel.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,15 @@ public readonly struct EventDataModel
1616
/// The event symbol that this model represents.
1717
/// </summary>
1818
public required IEventSymbol Event { get; init; }
19+
20+
/// <summary>
21+
/// Gets a value indicating whether the member is ambiguous due to diamond inheritance.
22+
/// </summary>
23+
public bool IsAmbiguous { get; init; }
1924
}
2025

2126
/// <summary>
22-
/// A method data model wrapping an <see cref="IEventSymbol"/>.
27+
/// An event data model wrapping an <see cref="IEventSymbol"/>.
2328
/// </summary>
2429
public readonly struct ResolvedEventSymbol
2530
{
@@ -32,4 +37,9 @@ public readonly struct ResolvedEventSymbol
3237
/// Gets a custom name to be applied to the method, if specified.
3338
/// </summary>
3439
public string? CustomName { get; init; }
40+
41+
/// <summary>
42+
/// Gets a value indicating whether the member is ambiguous due to diamond inheritance.
43+
/// </summary>
44+
public bool IsAmbiguous { get; init; }
3545
}

src/PolyType.Roslyn/Model/MethodDataModel.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public readonly struct MethodDataModel
3636
/// <summary>
3737
/// Whether the method is ambiguous due to diamond inheritance.
3838
/// </summary>
39-
public required bool IsDiamondAmbiguous { get; init; }
39+
public required bool IsAmbiguous { get; init; }
4040
}
4141

4242
/// <summary>
@@ -93,5 +93,5 @@ public readonly struct ResolvedMethodSymbol
9393
/// <summary>
9494
/// Gets a value indicating whether the method is ambiguous due to diamond inheritance.
9595
/// </summary>
96-
public bool IsDiamondAmbiguous { get; init; }
96+
public bool IsAmbiguous { get; init; }
9797
}

src/PolyType.Roslyn/Model/PropertyDataModel.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,16 @@ public PropertyDataModel(IFieldSymbol field)
5050
/// </summary>
5151
public string Name => PropertySymbol.Name;
5252

53+
/// <summary>
54+
/// A logical name overwriting the underlying name.
55+
/// </summary>
56+
public string? LogicalName { get; init; }
57+
58+
/// <summary>
59+
/// The declared order of the property or field.
60+
/// </summary>
61+
public int Order { get; init; }
62+
5363
/// <summary>
5464
/// The type exposed by this property.
5565
/// </summary>
@@ -104,6 +114,11 @@ public PropertyDataModel(IFieldSymbol field)
104114
/// </summary>
105115
public bool? IsRequiredByPolicy { get; init; }
106116

117+
/// <summary>
118+
/// Gets a value indicating whether the member is ambiguous due to diamond inheritance.
119+
/// </summary>
120+
public bool IsAmbiguous { get; init; }
121+
107122
/// <summary>
108123
/// Whether the property is init-only.
109124
/// </summary>
@@ -113,3 +128,39 @@ public PropertyDataModel(IFieldSymbol field)
113128
_ => false,
114129
};
115130
}
131+
132+
/// <summary>
133+
/// A property data model wrapping an <see cref="IEventSymbol"/>.
134+
/// </summary>
135+
public readonly struct ResolvedPropertySymbol
136+
{
137+
/// <summary>
138+
/// Gets the resolved property or field symbol.
139+
/// </summary>
140+
public required ISymbol Symbol { get; init; }
141+
142+
/// <summary>
143+
/// Gets a custom name to be applied to the member, if specified.
144+
/// </summary>
145+
public string? CustomName { get; init; }
146+
147+
/// <summary>
148+
/// Gets a value indicating whether the getter should be included in the shape.
149+
/// </summary>
150+
public bool IncludeGetter { get; init; }
151+
152+
/// <summary>
153+
/// Gets a value indicating whether the setter should be included in the shape.
154+
/// </summary>
155+
public bool IncludeSetter { get; init; }
156+
157+
/// <summary>
158+
/// The declared order of the property or field.
159+
/// </summary>
160+
public int Order { get; init; }
161+
162+
/// <summary>
163+
/// Gets a value indicating whether the member is ambiguous due to diamond inheritance.
164+
/// </summary>
165+
public bool IsAmbiguous { get; init; }
166+
}

src/PolyType.Roslyn/ModelGenerator/TypeDataModelGenerator.Dictionary.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,10 @@ bool ContainsInserter(ITypeSymbol type, ITypeSymbol keyType, ITypeSymbol valueTy
9898
{
9999
availableInsertionModes = DictionaryInsertionMode.None;
100100
bool foundContainsKey = false;
101-
var instanceMethods = type.GetAllMembers()
102-
.OfType<IMethodSymbol>()
103-
.Where(method => method.IsStatic is false && IsAccessibleSymbol(method));
101+
var instanceMethods = type.ResolveVisibleMembers<IMethodSymbol>()
102+
.Where(method => method.Symbol.IsStatic is false && IsAccessibleSymbol(method.Symbol));
104103

105-
foreach (var method in instanceMethods)
104+
foreach ((var method, _) in instanceMethods)
106105
{
107106
if (method.Parameters is [var p1, var p2] &&
108107
SymbolEqualityComparer.Default.Equals(p1.Type, keyType) &&

src/PolyType.Roslyn/ModelGenerator/TypeDataModelGenerator.Enumerable.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,11 +159,10 @@ private bool TryMapEnumerable(
159159
IMethodSymbol? ResolveAddMethod(ITypeSymbol type, ITypeSymbol elementType, out EnumerableInsertionMode insertionMode)
160160
{
161161
insertionMode = EnumerableInsertionMode.None;
162-
IMethodSymbol? result = type.GetAllMembers()
163-
.OfType<IMethodSymbol>()
162+
(IMethodSymbol? result, _) = type.ResolveVisibleMembers<IMethodSymbol>()
164163
.FirstOrDefault(method =>
165-
method is { DeclaredAccessibility: Accessibility.Public, IsStatic: false, Name: "Add" or "Enqueue" or "Push", Parameters: [{ Type: ITypeSymbol parameterType }] } &&
166-
SymbolEqualityComparer.Default.Equals(method.Parameters[0].Type, elementType));
164+
method.Symbol is { DeclaredAccessibility: Accessibility.Public, IsStatic: false, Name: "Add" or "Enqueue" or "Push", Parameters: [{ Type: ITypeSymbol parameterType }] } &&
165+
SymbolEqualityComparer.Default.Equals(method.Symbol.Parameters[0].Type, elementType));
167166

168167
if (result is not null)
169168
{

0 commit comments

Comments
 (0)