Merge pull request #7646 from hvitved/csharp/roslyn-tuple-elements-workaround

C#: Workaround Roslyn bug in `INamedTypeSymbol.TupleElements`
This commit is contained in:
Tom Hvitved
2022-01-19 19:54:29 +01:00
committed by GitHub
4 changed files with 56 additions and 15 deletions

View File

@@ -90,7 +90,11 @@ namespace Semmle.Extraction.CSharp.Entities
var tts = (TupleTypeSyntax)syntax;
var tt = (TupleType)type;
Emit(trapFile, loc ?? syntax.GetLocation(), parent, type);
tts.Elements.Zip(tt.TupleElements, (s, t) => Create(Context, s.Type, this, t.Type)).Enumerate();
foreach (var (s, t) in tts.Elements.Zip(tt.TupleElements, (s, t) => (s, t?.Type)))
{
if (t is not null)
Create(Context, s.Type, this, t);
}
return;
case SyntaxKind.GenericName:
Emit(trapFile, loc ?? syntax.GetLocation(), parent, type);

View File

@@ -24,7 +24,7 @@ namespace Semmle.Extraction.CSharp.Entities
private TupleType(Context cx, INamedTypeSymbol init) : base(cx, init)
{
tupleElementsLazy = new Lazy<Field[]>(() => Symbol.TupleElements.Select(t => Field.Create(cx, t)).ToArray());
tupleElementsLazy = new Lazy<Field?[]>(() => Symbol.GetTupleElementsMaybeNull().Select(t => t is null ? null : Field.Create(cx, t)).ToArray());
}
// All tuple types are "local types"
@@ -47,7 +47,10 @@ namespace Semmle.Extraction.CSharp.Entities
var index = 0;
foreach (var element in TupleElements)
trapFile.tuple_element(this, index++, element);
{
if (element is not null)
trapFile.tuple_element(this, index++, element);
}
// Note: symbol.Locations seems to be very inconsistent
// about what locations are available for a tuple type.
@@ -56,9 +59,10 @@ namespace Semmle.Extraction.CSharp.Entities
trapFile.type_location(this, Context.CreateLocation(l));
}
private readonly Lazy<Field[]> tupleElementsLazy;
public Field[] TupleElements => tupleElementsLazy.Value;
private readonly Lazy<Field?[]> tupleElementsLazy;
public Field?[] TupleElements => tupleElementsLazy.Value;
public override IEnumerable<Type> TypeMentions => TupleElements.Select(e => e.Type);
public override IEnumerable<Type> TypeMentions =>
TupleElements.OfType<Field>().Select(e => e.Type);
}
}

View File

@@ -280,17 +280,30 @@ namespace Semmle.Extraction.CSharp
private static void BuildFunctionPointerTypeId(this IFunctionPointerTypeSymbol funptr, Context cx, EscapingTextWriter trapFile, ISymbol symbolBeingDefined) =>
BuildFunctionPointerSignature(funptr, trapFile, s => s.BuildOrWriteId(cx, trapFile, symbolBeingDefined));
/// <summary>
/// Workaround for a Roslyn bug: https://github.com/dotnet/roslyn/issues/53943
/// </summary>
public static IEnumerable<IFieldSymbol?> GetTupleElementsMaybeNull(this INamedTypeSymbol type) =>
type.TupleElements;
private static void BuildNamedTypeId(this INamedTypeSymbol named, Context cx, EscapingTextWriter trapFile, ISymbol symbolBeingDefined, bool constructUnderlyingTupleType)
{
if (!constructUnderlyingTupleType && named.IsTupleType)
{
trapFile.Write('(');
trapFile.BuildList(",", named.TupleElements,
f =>
trapFile.BuildList(",", named.GetTupleElementsMaybeNull(),
(i, f) =>
{
trapFile.Write((f.CorrespondingTupleField ?? f).Name);
trapFile.Write(":");
f.Type.BuildOrWriteId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType: false);
if (f is null)
{
trapFile.Write($"null({i})");
}
else
{
trapFile.Write((f.CorrespondingTupleField ?? f).Name);
trapFile.Write(":");
f.Type.BuildOrWriteId(cx, trapFile, symbolBeingDefined, constructUnderlyingTupleType: false);
}
}
);
trapFile.Write(")");
@@ -464,8 +477,14 @@ namespace Semmle.Extraction.CSharp
trapFile.Write('(');
trapFile.BuildList(
",",
namedType.TupleElements.Select(f => f.Type),
t => t.BuildDisplayName(cx, trapFile));
namedType.GetTupleElementsMaybeNull(),
(i, f) =>
{
if (f is null)
trapFile.Write($"null({i})");
else
f.Type.BuildDisplayName(cx, trapFile);
});
trapFile.Write(")");
return;
}

View File

@@ -232,19 +232,33 @@ namespace Semmle.Extraction
/// <param name="items">The list of items.</param>
/// <param name="action">The action on each item.</param>
/// <returns>The original trap builder (fluent interface).</returns>
public static T1 BuildList<T1, T2>(this T1 trapFile, string separator, IEnumerable<T2> items, Action<T2> action)
public static T1 BuildList<T1, T2>(this T1 trapFile, string separator, IEnumerable<T2> items, Action<int, T2> action)
where T1 : TextWriter
{
var first = true;
var i = 0;
foreach (var item in items)
{
if (first)
first = false;
else
trapFile.Write(separator);
action(item);
action(i++, item);
}
return trapFile;
}
/// <summary>
/// Builds a trap builder using a separator and an action for each item in the list.
/// </summary>
/// <typeparam name="T">The type of the items.</typeparam>
/// <param name="trapFile">The trap builder to append to.</param>
/// <param name="separator">The separator string (e.g. ",")</param>
/// <param name="items">The list of items.</param>
/// <param name="action">The action on each item.</param>
/// <returns>The original trap builder (fluent interface).</returns>
public static T1 BuildList<T1, T2>(this T1 trapFile, string separator, IEnumerable<T2> items, Action<T2> action)
where T1 : TextWriter =>
trapFile.BuildList(separator, items, (_, item) => action(item));
}
}